mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-4248: Task type (#4042)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
58b841518a
commit
19d6764250
2
.gitignore
vendored
2
.gitignore
vendored
@ -86,4 +86,4 @@ dev/tool/report*.csv
|
||||
tests/db_dump
|
||||
.build
|
||||
.format
|
||||
tools/apm/apm.js
|
||||
tools/apm/apm.js
|
||||
|
@ -37,7 +37,8 @@ const object: AttachedData<Issue> = {
|
||||
remainingTime: 0,
|
||||
estimation: 0,
|
||||
reports: 0,
|
||||
childInfo: []
|
||||
childInfo: [],
|
||||
kind: tracker.taskTypes.Issue
|
||||
}
|
||||
|
||||
export interface IssueOptions {
|
||||
@ -100,7 +101,8 @@ async function genIssue (client: TxOperations, statuses: Ref<IssueStatus>[]): Pr
|
||||
estimation: object.estimation,
|
||||
reports: 0,
|
||||
relations: [],
|
||||
childInfo: []
|
||||
childInfo: [],
|
||||
kind: tracker.taskTypes.Issue
|
||||
}
|
||||
await client.addCollection(
|
||||
tracker.class.Issue,
|
||||
|
@ -15,7 +15,7 @@ import core, {
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import recruit from '@hcengineering/model-recruit'
|
||||
import { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
||||
import task, { ProjectType, genRanks } from '@hcengineering/task'
|
||||
import task, { ProjectType, TaskType, genRanks } from '@hcengineering/task'
|
||||
import faker from 'faker'
|
||||
import jpeg, { BufferRet } from 'jpeg-js'
|
||||
import { AttachmentOptions, addAttachments } from './attachments'
|
||||
@ -97,13 +97,16 @@ async function genVacansyApplicants (
|
||||
name: faker.name.title(),
|
||||
description: faker.lorem.sentences(2),
|
||||
shortDescription: faker.lorem.sentences(1),
|
||||
category: recruit.category.VacancyTypeCategories,
|
||||
descriptor: recruit.descriptors.VacancyType,
|
||||
private: false,
|
||||
members: [],
|
||||
archived: false,
|
||||
tasks: [],
|
||||
// TODO: Fix me.
|
||||
statuses: states.map((s) => {
|
||||
return { _id: s }
|
||||
})
|
||||
return { _id: s, taskType: '' as Ref<TaskType> }
|
||||
}),
|
||||
targetClass: recruit.class.Vacancy
|
||||
}
|
||||
|
||||
await ctx.with('update', {}, (ctx) =>
|
||||
@ -180,7 +183,8 @@ async function genApplicant (
|
||||
status: faker.random.arrayElement(states),
|
||||
rank,
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
dueDate: null,
|
||||
kind: recruit.taskTypes.Applicant
|
||||
}
|
||||
|
||||
// Update or create candidate
|
||||
|
@ -516,16 +516,26 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
task.class.ProjectTypeCategory,
|
||||
task.class.ProjectTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
name: board.string.Boards,
|
||||
name: board.string.BoardApplication,
|
||||
description: board.string.ManageBoardStatuses,
|
||||
icon: board.component.TemplatesIcon,
|
||||
attachedToClass: board.class.Board,
|
||||
statusClass: core.class.Status,
|
||||
statusCategories: [task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost]
|
||||
baseClass: board.class.Board
|
||||
},
|
||||
board.category.BoardType
|
||||
board.descriptors.BoardType
|
||||
)
|
||||
builder.createDoc(
|
||||
task.class.TaskTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
baseClass: board.class.Card,
|
||||
allowCreate: true,
|
||||
description: board.string.Card,
|
||||
icon: board.icon.Card,
|
||||
name: board.string.Card
|
||||
},
|
||||
board.descriptors.Card
|
||||
)
|
||||
}
|
||||
|
@ -13,19 +13,21 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { boardId } from '@hcengineering/board'
|
||||
import { type Ref, TxOperations } from '@hcengineering/core'
|
||||
import {
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
createOrUpdate
|
||||
createOrUpdate,
|
||||
tryMigrate
|
||||
} from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import { createProjectType, createSequence } from '@hcengineering/model-task'
|
||||
import tags from '@hcengineering/model-tags'
|
||||
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
import { createProjectType, createSequence, fixTaskTypes } from '@hcengineering/model-task'
|
||||
import tags from '@hcengineering/tags'
|
||||
import task, { type ProjectType } from '@hcengineering/task'
|
||||
import board from './plugin'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
import board from './plugin'
|
||||
|
||||
async function createSpace (tx: TxOperations): Promise<void> {
|
||||
const current = await tx.findOne(core.class.Space, {
|
||||
@ -54,27 +56,51 @@ async function createDefaultProjectType (tx: TxOperations): Promise<Ref<ProjectT
|
||||
tx,
|
||||
{
|
||||
name: 'Default board',
|
||||
category: board.category.BoardType,
|
||||
description: ''
|
||||
descriptor: board.descriptors.BoardType,
|
||||
description: '',
|
||||
tasks: []
|
||||
},
|
||||
[
|
||||
{
|
||||
color: PaletteColorIndexes.Blueberry,
|
||||
name: 'To do',
|
||||
category: task.statusCategory.Active,
|
||||
ofAttribute: board.attribute.State
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Arctic,
|
||||
name: 'Done',
|
||||
category: task.statusCategory.Active,
|
||||
ofAttribute: board.attribute.State
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Grass,
|
||||
name: 'Completed',
|
||||
category: board.statusCategory.Completed,
|
||||
ofAttribute: board.attribute.State
|
||||
_id: board.taskType.Card,
|
||||
descriptor: board.descriptors.Card,
|
||||
name: 'Card',
|
||||
ofClass: board.class.Card,
|
||||
targetClass: board.class.Card,
|
||||
statusClass: core.class.Status,
|
||||
kind: 'task',
|
||||
factory: [
|
||||
{
|
||||
color: PaletteColorIndexes.Coin,
|
||||
name: 'Unstarted',
|
||||
category: task.statusCategory.UnStarted,
|
||||
ofAttribute: board.attribute.State
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Blueberry,
|
||||
name: 'To do',
|
||||
category: task.statusCategory.Active,
|
||||
ofAttribute: board.attribute.State
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Arctic,
|
||||
name: 'Done',
|
||||
category: task.statusCategory.Active,
|
||||
ofAttribute: board.attribute.State
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Grass,
|
||||
name: 'Completed',
|
||||
category: board.statusCategory.Completed,
|
||||
ofAttribute: board.attribute.State
|
||||
}
|
||||
],
|
||||
statusCategories: [
|
||||
task.statusCategory.UnStarted,
|
||||
task.statusCategory.Active,
|
||||
task.statusCategory.Won,
|
||||
task.statusCategory.Lost
|
||||
]
|
||||
}
|
||||
],
|
||||
board.template.DefaultBoard
|
||||
@ -100,7 +126,44 @@ async function createDefaults (tx: TxOperations): Promise<void> {
|
||||
}
|
||||
|
||||
export const boardOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, boardId, [
|
||||
{
|
||||
state: 'fix-category-descriptors',
|
||||
func: async (client) => {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{ _class: task.class.ProjectType, category: 'board:category:BoardType' },
|
||||
{
|
||||
$set: { descriptor: board.descriptors.BoardType },
|
||||
$unset: { category: 1 }
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'fixTaskStatus',
|
||||
func: async (client): Promise<void> => {
|
||||
await fixTaskTypes(client, board.descriptors.BoardType, async () => [
|
||||
{
|
||||
name: 'Card',
|
||||
descriptor: board.descriptors.Card,
|
||||
ofClass: board.class.Card,
|
||||
targetClass: board.class.Card,
|
||||
statusCategories: [
|
||||
task.statusCategory.UnStarted,
|
||||
task.statusCategory.Active,
|
||||
task.statusCategory.Won,
|
||||
task.statusCategory.Lost
|
||||
],
|
||||
statusClass: core.class.Status,
|
||||
kind: 'task'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const ops = new TxOperations(client, core.account.System)
|
||||
await createDefaults(ops)
|
||||
|
@ -18,7 +18,7 @@ import { type Board, boardId } from '@hcengineering/board'
|
||||
import board from '@hcengineering/board-resources/src/plugin'
|
||||
import type { Ref } from '@hcengineering/core'
|
||||
import { type IntlString, mergeIds } from '@hcengineering/platform'
|
||||
import { type ProjectType, type Sequence } from '@hcengineering/task'
|
||||
import { type TaskTypeDescriptor, type ProjectType, type Sequence } from '@hcengineering/task'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ViewAction, type Viewlet, type ViewletDescriptor } from '@hcengineering/view'
|
||||
|
||||
@ -67,5 +67,8 @@ export default mergeIds(boardId, board, {
|
||||
},
|
||||
actionImpl: {
|
||||
ConvertToCard: '' as ViewAction
|
||||
},
|
||||
descriptors: {
|
||||
Card: '' as Ref<TaskTypeDescriptor>
|
||||
}
|
||||
})
|
||||
|
@ -54,7 +54,6 @@ export default mergeIds(contactId, contact, {
|
||||
ContactArrayEditor: '' as AnyComponent,
|
||||
EmployeeEditor: '' as AnyComponent,
|
||||
CreateEmployee: '' as AnyComponent,
|
||||
AccountArrayEditor: '' as AnyComponent,
|
||||
ChannelFilter: '' as AnyComponent,
|
||||
MergePersons: '' as AnyComponent,
|
||||
ChannelPanel: '' as AnyComponent,
|
||||
|
@ -689,16 +689,26 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
task.class.ProjectTypeCategory,
|
||||
task.class.ProjectTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
name: lead.string.Funnels,
|
||||
name: lead.string.LeadApplication,
|
||||
description: lead.string.ManageFunnelStatuses,
|
||||
icon: lead.component.TemplatesIcon,
|
||||
attachedToClass: lead.class.Funnel,
|
||||
statusClass: core.class.Status,
|
||||
statusCategories: [task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost]
|
||||
baseClass: lead.class.Funnel
|
||||
},
|
||||
lead.category.FunnelTypeCategory
|
||||
lead.descriptors.FunnelType
|
||||
)
|
||||
builder.createDoc(
|
||||
task.class.TaskTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
baseClass: lead.class.Lead,
|
||||
allowCreate: true,
|
||||
description: lead.string.Lead,
|
||||
icon: lead.icon.Lead,
|
||||
name: lead.string.Lead
|
||||
},
|
||||
lead.descriptors.Lead
|
||||
)
|
||||
}
|
||||
|
@ -16,15 +16,14 @@
|
||||
import { TxOperations } from '@hcengineering/core'
|
||||
import { leadId } from '@hcengineering/lead'
|
||||
import {
|
||||
tryMigrate,
|
||||
tryUpgrade,
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
tryUpgrade
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import { createProjectType, createSequence } from '@hcengineering/model-task'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import task from '@hcengineering/task'
|
||||
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
import task, { createProjectType, createSequence, fixTaskTypes } from '@hcengineering/model-task'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
import lead from './plugin'
|
||||
|
||||
@ -37,42 +36,66 @@ async function createSpace (tx: TxOperations): Promise<void> {
|
||||
tx,
|
||||
{
|
||||
name: 'Default funnel',
|
||||
category: lead.category.FunnelTypeCategory,
|
||||
description: ''
|
||||
descriptor: lead.descriptors.FunnelType,
|
||||
description: '',
|
||||
tasks: []
|
||||
},
|
||||
[
|
||||
{
|
||||
color: PaletteColorIndexes.Coin,
|
||||
name: 'Incoming',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Arctic,
|
||||
name: 'Negotation',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Watermelon,
|
||||
name: 'Offer preparing',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Orange,
|
||||
name: 'Make a decision',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Ocean,
|
||||
name: 'Contract conclusion',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{ name: 'Won', ofAttribute: lead.attribute.State, category: task.statusCategory.Won },
|
||||
{ name: 'Lost', ofAttribute: lead.attribute.State, category: task.statusCategory.Lost }
|
||||
_id: lead.taskType.Lead,
|
||||
name: 'Lead',
|
||||
descriptor: lead.descriptors.Lead,
|
||||
ofClass: lead.class.Lead,
|
||||
targetClass: lead.class.Lead,
|
||||
statusClass: core.class.Status,
|
||||
statusCategories: [
|
||||
task.statusCategory.UnStarted,
|
||||
task.statusCategory.Active,
|
||||
task.statusCategory.Won,
|
||||
task.statusCategory.Lost
|
||||
],
|
||||
kind: 'task',
|
||||
factory: [
|
||||
{
|
||||
color: PaletteColorIndexes.Coin,
|
||||
name: 'Backlog',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.UnStarted
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Coin,
|
||||
name: 'Incoming',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Arctic,
|
||||
name: 'Negotation',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Watermelon,
|
||||
name: 'Offer preparing',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Orange,
|
||||
name: 'Make a decision',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Ocean,
|
||||
name: 'Contract conclusion',
|
||||
ofAttribute: lead.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{ name: 'Won', ofAttribute: lead.attribute.State, category: task.statusCategory.Won },
|
||||
{ name: 'Lost', ofAttribute: lead.attribute.State, category: task.statusCategory.Lost }
|
||||
]
|
||||
}
|
||||
],
|
||||
lead.template.DefaultFunnel
|
||||
)
|
||||
@ -98,24 +121,48 @@ async function createDefaults (tx: TxOperations): Promise<void> {
|
||||
}
|
||||
|
||||
export const leadOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, leadId, [
|
||||
{
|
||||
state: 'fix-category-descriptors',
|
||||
func: async (client) => {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{ _class: task.class.ProjectType, category: 'lead:category:FunnelTypeCategory' },
|
||||
{
|
||||
$set: { descriptor: lead.descriptors.FunnelType },
|
||||
$unset: { category: 1 }
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'fixTaskStatus',
|
||||
func: async (client): Promise<void> => {
|
||||
await fixTaskTypes(client, lead.descriptors.FunnelType, async () => [
|
||||
{
|
||||
name: 'Lead',
|
||||
descriptor: lead.descriptors.Lead,
|
||||
ofClass: lead.class.Lead,
|
||||
targetClass: lead.class.Lead,
|
||||
statusCategories: [
|
||||
task.statusCategory.UnStarted,
|
||||
task.statusCategory.Active,
|
||||
task.statusCategory.Won,
|
||||
task.statusCategory.Lost
|
||||
],
|
||||
statusClass: core.class.Status,
|
||||
kind: 'task'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const ops = new TxOperations(client, core.account.System)
|
||||
await createDefaults(ops)
|
||||
|
||||
await tryUpgrade(client, leadId, [
|
||||
{
|
||||
state: 'related-targets',
|
||||
func: async (client): Promise<void> => {
|
||||
const ops = new TxOperations(client, core.account.ConfigUser)
|
||||
await ops.createDoc(tracker.class.RelatedIssueTarget, core.space.Configuration, {
|
||||
rule: {
|
||||
kind: 'classRule',
|
||||
ofClass: lead.class.Lead
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
])
|
||||
await tryUpgrade(client, leadId, [])
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import lead from '@hcengineering/lead-resources/src/plugin'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { type ProjectType } from '@hcengineering/task'
|
||||
import { type TaskTypeDescriptor, type ProjectType } from '@hcengineering/task'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ActionCategory, type Viewlet } from '@hcengineering/view'
|
||||
|
||||
@ -72,5 +72,8 @@ export default mergeIds(leadId, lead, {
|
||||
LeadCreateNotification: '' as Ref<NotificationType>,
|
||||
AssigneeNotification: '' as Ref<NotificationType>,
|
||||
LeadChatMessageViewlet: '' as Ref<ChatMessageViewlet>
|
||||
},
|
||||
descriptors: {
|
||||
Lead: '' as Ref<TaskTypeDescriptor>
|
||||
}
|
||||
})
|
||||
|
@ -276,9 +276,9 @@ export function createModel (builder: Builder): void {
|
||||
label: notification.string.Notifications,
|
||||
icon: notification.icon.Notifications,
|
||||
component: notification.component.NotificationSettings,
|
||||
group: 'settings',
|
||||
group: 'settings-account',
|
||||
secured: false,
|
||||
order: 2500
|
||||
order: 1500
|
||||
},
|
||||
notification.ids.NotificationSettings
|
||||
)
|
||||
|
@ -316,7 +316,7 @@ export function createModel (builder: Builder): void {
|
||||
label: recruit.string.Applications,
|
||||
createLabel: recruit.string.ApplicationCreateLabel,
|
||||
createComponent: recruit.component.CreateApplication,
|
||||
category: recruit.category.VacancyTypeCategories,
|
||||
descriptor: recruit.descriptors.VacancyType,
|
||||
descriptors: [
|
||||
view.viewlet.Table,
|
||||
view.viewlet.List,
|
||||
@ -1750,17 +1750,28 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
task.class.ProjectTypeCategory,
|
||||
task.class.ProjectTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
name: recruit.string.Vacancies,
|
||||
name: recruit.string.RecruitApplication,
|
||||
description: recruit.string.ManageVacancyStatuses,
|
||||
icon: recruit.component.TemplatesIcon,
|
||||
editor: recruit.component.VacancyTemplateEditor,
|
||||
attachedToClass: recruit.class.Vacancy,
|
||||
statusClass: core.class.Status,
|
||||
statusCategories: [task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost]
|
||||
baseClass: recruit.class.Vacancy
|
||||
},
|
||||
recruit.category.VacancyTypeCategories
|
||||
recruit.descriptors.VacancyType
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
task.class.TaskTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
baseClass: recruit.class.Applicant,
|
||||
allowCreate: true,
|
||||
description: recruit.string.Application,
|
||||
icon: recruit.icon.Application,
|
||||
name: recruit.string.Application
|
||||
},
|
||||
recruit.descriptors.Application
|
||||
)
|
||||
}
|
||||
|
@ -14,70 +14,67 @@
|
||||
//
|
||||
|
||||
import { getCategories } from '@anticrm/skillset'
|
||||
import core, { type Ref, TxOperations } from '@hcengineering/core'
|
||||
import core, { TxOperations, type Ref } from '@hcengineering/core'
|
||||
import {
|
||||
createOrUpdate,
|
||||
tryMigrate,
|
||||
tryUpgrade,
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
createOrUpdate,
|
||||
tryUpgrade
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import tags, { type TagCategory } from '@hcengineering/model-tags'
|
||||
import { createProjectType, createSequence } from '@hcengineering/model-task'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import { createProjectType, createSequence, fixTaskTypes } from '@hcengineering/model-task'
|
||||
import { recruitId } from '@hcengineering/recruit'
|
||||
import task, { type ProjectType } from '@hcengineering/task'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
import recruit from './plugin'
|
||||
import { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
|
||||
export const recruitOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, recruitId, [
|
||||
{
|
||||
state: 'fix-category-descriptors',
|
||||
func: async (client) => {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{ _class: task.class.ProjectType, category: 'recruit:category:VacancyTypeCategories' },
|
||||
{
|
||||
$set: { descriptor: recruit.descriptors.VacancyType },
|
||||
$unset: { category: 1 }
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'fixTaskStatus',
|
||||
func: async (client): Promise<void> => {
|
||||
await fixTaskTypes(client, recruit.descriptors.VacancyType, async () => [
|
||||
{
|
||||
name: 'Applicant',
|
||||
descriptor: recruit.descriptors.Application,
|
||||
ofClass: recruit.class.Applicant,
|
||||
targetClass: recruit.class.Applicant,
|
||||
statusCategories: [
|
||||
task.statusCategory.UnStarted,
|
||||
task.statusCategory.Active,
|
||||
task.statusCategory.Won,
|
||||
task.statusCategory.Lost
|
||||
],
|
||||
statusClass: core.class.Status,
|
||||
kind: 'task'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
await createDefaults(tx)
|
||||
|
||||
await tryUpgrade(client, recruitId, [
|
||||
{
|
||||
state: 'related-targets',
|
||||
func: async (client): Promise<void> => {
|
||||
const ops = new TxOperations(client, core.account.ConfigUser)
|
||||
await ops.createDoc(tracker.class.RelatedIssueTarget, core.space.Configuration, {
|
||||
rule: {
|
||||
kind: 'classRule',
|
||||
ofClass: recruit.class.Vacancy
|
||||
}
|
||||
})
|
||||
|
||||
await ops.createDoc(tracker.class.RelatedIssueTarget, core.space.Configuration, {
|
||||
rule: {
|
||||
kind: 'classRule',
|
||||
ofClass: recruit.class.Applicant
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'wrong-categories',
|
||||
func: async (client): Promise<void> => {
|
||||
const ops = new TxOperations(client, core.account.System)
|
||||
while (true) {
|
||||
const docs = await ops.findAll(
|
||||
tags.class.TagElement,
|
||||
{
|
||||
targetClass: recruit.mixin.Candidate,
|
||||
category: { $in: [tracker.category.Other, 'document:category:Other' as Ref<TagCategory>] }
|
||||
},
|
||||
{ limit: 1000 }
|
||||
)
|
||||
for (const d of docs) {
|
||||
await ops.update(d, { category: recruit.category.Other })
|
||||
}
|
||||
if (docs.length === 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'remove-members',
|
||||
func: async (client): Promise<void> => {
|
||||
@ -137,36 +134,60 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise<Ref<Proje
|
||||
tx,
|
||||
{
|
||||
name: 'Default vacancy',
|
||||
category: recruit.category.VacancyTypeCategories,
|
||||
description: ''
|
||||
descriptor: recruit.descriptors.VacancyType,
|
||||
description: '',
|
||||
tasks: []
|
||||
},
|
||||
[
|
||||
{
|
||||
color: PaletteColorIndexes.Coin,
|
||||
name: 'HR Interview',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Cerulean,
|
||||
name: 'Technical Interview',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Waterway,
|
||||
name: 'Test task',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Grass,
|
||||
name: 'Offer',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{ name: 'Won', ofAttribute: recruit.attribute.State, category: task.statusCategory.Won },
|
||||
{ name: 'Lost', ofAttribute: recruit.attribute.State, category: task.statusCategory.Lost }
|
||||
_id: recruit.taskTypes.Applicant,
|
||||
name: 'Applicant',
|
||||
descriptor: recruit.descriptors.Application,
|
||||
ofClass: recruit.class.Applicant,
|
||||
targetClass: recruit.class.Applicant,
|
||||
statusCategories: [
|
||||
task.statusCategory.UnStarted,
|
||||
task.statusCategory.Active,
|
||||
task.statusCategory.Won,
|
||||
task.statusCategory.Lost
|
||||
],
|
||||
statusClass: core.class.Status,
|
||||
kind: 'task',
|
||||
factory: [
|
||||
{
|
||||
color: PaletteColorIndexes.Coin,
|
||||
name: 'Backlog',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.UnStarted
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Coin,
|
||||
name: 'HR Interview',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Cerulean,
|
||||
name: 'Technical Interview',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Waterway,
|
||||
name: 'Test task',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{
|
||||
color: PaletteColorIndexes.Grass,
|
||||
name: 'Offer',
|
||||
ofAttribute: recruit.attribute.State,
|
||||
category: task.statusCategory.Active
|
||||
},
|
||||
{ name: 'Won', ofAttribute: recruit.attribute.State, category: task.statusCategory.Won },
|
||||
{ name: 'Lost', ofAttribute: recruit.attribute.State, category: task.statusCategory.Lost }
|
||||
]
|
||||
}
|
||||
],
|
||||
recruit.template.DefaultVacancy
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ import type { IntlString, Resource, Status } from '@hcengineering/platform'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { recruitId } from '@hcengineering/recruit'
|
||||
import recruit from '@hcengineering/recruit-resources/src/plugin'
|
||||
import { type ProjectType } from '@hcengineering/task'
|
||||
import { type TaskTypeDescriptor, type ProjectType } from '@hcengineering/task'
|
||||
import type { AnyComponent, Location } from '@hcengineering/ui/src/types'
|
||||
import type { Action, ActionCategory, ViewAction, ViewQueryAction, Viewlet } from '@hcengineering/view'
|
||||
import { type DocUpdateMessageViewlet } from '@hcengineering/activity'
|
||||
@ -138,5 +138,8 @@ export default mergeIds(recruitId, recruit, {
|
||||
TableReview: '' as Ref<Viewlet>,
|
||||
TableVacancyList: '' as Ref<Viewlet>,
|
||||
ApplicantDashboard: '' as Ref<Viewlet>
|
||||
},
|
||||
descriptors: {
|
||||
Application: '' as Ref<TaskTypeDescriptor>
|
||||
}
|
||||
})
|
||||
|
@ -15,23 +15,22 @@
|
||||
|
||||
import activity from '@hcengineering/activity'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { type Account, type Domain, DOMAIN_MODEL, type Ref } from '@hcengineering/core'
|
||||
import { type Builder, Mixin, Model } from '@hcengineering/model'
|
||||
import { DOMAIN_MODEL, type Account, type Domain, type Ref } from '@hcengineering/core'
|
||||
import { Mixin, Model, type Builder } from '@hcengineering/model'
|
||||
import core, { TClass, TConfiguration, TDoc } from '@hcengineering/model-core'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import notification from '@hcengineering/notification'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import {
|
||||
settingId,
|
||||
type Editable,
|
||||
type Handler,
|
||||
type Integration,
|
||||
type IntegrationType,
|
||||
type InviteSettings,
|
||||
settingId,
|
||||
type SettingsCategory,
|
||||
type UserMixin
|
||||
} from '@hcengineering/setting'
|
||||
import task from '@hcengineering/task'
|
||||
import templates from '@hcengineering/templates'
|
||||
import setting from './plugin'
|
||||
|
||||
@ -39,8 +38,8 @@ import workbench from '@hcengineering/model-workbench'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
|
||||
export { settingId } from '@hcengineering/setting'
|
||||
export { default } from './plugin'
|
||||
export { settingOperation } from './migration'
|
||||
export { default } from './plugin'
|
||||
|
||||
export const DOMAIN_SETTING = 'setting' as Domain
|
||||
|
||||
@ -153,6 +152,9 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.WorkspaceSetting,
|
||||
icon: setting.icon.Setting,
|
||||
component: setting.component.WorkspaceSettings,
|
||||
extraComponents: {
|
||||
navigation: setting.component.WorkspaceSettings
|
||||
},
|
||||
group: 'settings',
|
||||
secured: false,
|
||||
order: 2000
|
||||
@ -167,9 +169,9 @@ export function createModel (builder: Builder): void {
|
||||
label: setting.string.Integrations,
|
||||
icon: setting.icon.Integrations,
|
||||
component: setting.component.Integrations,
|
||||
group: 'settings',
|
||||
group: 'settings-account',
|
||||
secured: false,
|
||||
order: 3000
|
||||
order: 1500
|
||||
},
|
||||
setting.ids.Integrations
|
||||
)
|
||||
@ -200,21 +202,7 @@ export function createModel (builder: Builder): void {
|
||||
setting.ids.Configure
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'statuses',
|
||||
label: setting.string.ManageProjects,
|
||||
icon: task.icon.ManageTemplates,
|
||||
component: setting.component.ManageProjects,
|
||||
group: 'settings-editor',
|
||||
secured: false,
|
||||
order: 4000
|
||||
},
|
||||
setting.ids.ManageProjects
|
||||
)
|
||||
builder.createDoc(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
setting.class.SettingsCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'classes',
|
||||
@ -237,7 +225,8 @@ export function createModel (builder: Builder): void {
|
||||
component: setting.component.EnumSetting,
|
||||
group: 'settings-editor',
|
||||
secured: false,
|
||||
order: 4600
|
||||
order: 4600,
|
||||
expandable: true
|
||||
},
|
||||
setting.ids.EnumSetting
|
||||
)
|
||||
|
@ -42,6 +42,7 @@
|
||||
"@hcengineering/task-resources": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.11",
|
||||
"@hcengineering/view": "^0.6.9",
|
||||
"@hcengineering/workbench": "^0.6.9"
|
||||
"@hcengineering/setting": "^0.6.11",
|
||||
"@hcengineering/notification": "^0.6.16"
|
||||
}
|
||||
}
|
||||
|
@ -16,18 +16,25 @@
|
||||
import type { Employee, Person } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact'
|
||||
import {
|
||||
type Class,
|
||||
ClassifierKind,
|
||||
DOMAIN_MODEL,
|
||||
DOMAIN_STATUS,
|
||||
DOMAIN_TX,
|
||||
IndexKind,
|
||||
generateId,
|
||||
type Class,
|
||||
type Data,
|
||||
type Doc,
|
||||
type Domain,
|
||||
IndexKind,
|
||||
type Ref,
|
||||
type Status,
|
||||
type StatusCategory,
|
||||
type Timestamp
|
||||
type Timestamp,
|
||||
type TxCreateDoc,
|
||||
type TxMixin
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
type Builder,
|
||||
ArrOf,
|
||||
Collection,
|
||||
Hidden,
|
||||
Index,
|
||||
@ -37,34 +44,50 @@ import {
|
||||
TypeBoolean,
|
||||
TypeDate,
|
||||
TypeMarkup,
|
||||
TypeRecord,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX
|
||||
UX,
|
||||
type Builder,
|
||||
type MigrationClient
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@hcengineering/model-core'
|
||||
import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view'
|
||||
import { type IntlString } from '@hcengineering/platform'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import core, { DOMAIN_SPACE, TAttachedDoc, TClass, TDoc, TSpace } from '@hcengineering/model-core'
|
||||
import view, {
|
||||
classPresenter,
|
||||
createAction,
|
||||
template,
|
||||
actionTemplates as viewTemplates
|
||||
} from '@hcengineering/model-view'
|
||||
import { getEmbeddedLabel, type Asset, type IntlString } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import tags from '@hcengineering/tags'
|
||||
import {
|
||||
calculateStatuses,
|
||||
findStatusAttr,
|
||||
type KanbanCard,
|
||||
type Project,
|
||||
type ProjectStatus,
|
||||
type ProjectType,
|
||||
type ProjectTypeCategory,
|
||||
type ProjectTypeClass,
|
||||
type ProjectTypeDescriptor,
|
||||
type Sequence,
|
||||
type Task,
|
||||
type TaskType,
|
||||
type TaskTypeClass,
|
||||
type TaskTypeDescriptor,
|
||||
type TaskTypeKind,
|
||||
type TodoItem
|
||||
} from '@hcengineering/task'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
import { type ViewAction } from '@hcengineering/view'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
|
||||
import task from './plugin'
|
||||
|
||||
export { taskId } from '@hcengineering/task'
|
||||
export { createProjectType, createSequence, taskOperation } from './migration'
|
||||
export { createProjectType, taskId } from '@hcengineering/task'
|
||||
export { createSequence, taskOperation } from './migration'
|
||||
export { default } from './plugin'
|
||||
|
||||
export const DOMAIN_TASK = 'task' as Domain
|
||||
@ -80,6 +103,10 @@ export class TTask extends TAttachedDoc implements Task {
|
||||
@Index(IndexKind.Indexed)
|
||||
status!: Ref<Status>
|
||||
|
||||
@Prop(TypeRef(task.class.TaskType), task.string.TaskType)
|
||||
@Index(IndexKind.Indexed)
|
||||
kind!: Ref<TaskType>
|
||||
|
||||
@Prop(TypeString(), task.string.TaskNumber)
|
||||
@Index(IndexKind.FullText)
|
||||
@Hidden()
|
||||
@ -132,27 +159,91 @@ export class TKanbanCard extends TClass implements KanbanCard {
|
||||
card!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(task.class.TaskTypeDescriptor, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TTaskTypeDescriptor extends TDoc implements TaskTypeDescriptor {
|
||||
name!: IntlString
|
||||
description!: IntlString
|
||||
icon!: Asset
|
||||
baseClass!: Ref<Class<Task>>
|
||||
|
||||
// If specified, will allow to be created by users, system type overwize
|
||||
allowCreate!: boolean
|
||||
}
|
||||
|
||||
@Mixin(task.mixin.TaskTypeClass, core.class.Class)
|
||||
export class TTaskTypeClass extends TClass implements TaskTypeClass {
|
||||
taskType!: Ref<TaskType>
|
||||
projectType!: Ref<ProjectType>
|
||||
}
|
||||
|
||||
@Mixin(task.mixin.ProjectTypeClass, core.class.Class)
|
||||
export class TProjectTypeClass extends TClass implements ProjectTypeClass {
|
||||
projectType!: Ref<ProjectType>
|
||||
}
|
||||
|
||||
@Model(task.class.Project, core.class.Space)
|
||||
export class TProject extends TSpace implements Project {
|
||||
type!: Ref<ProjectType>
|
||||
@Prop(TypeRef(task.class.ProjectType), task.string.ProjectType)
|
||||
type!: Ref<ProjectType>
|
||||
}
|
||||
|
||||
@Model(task.class.ProjectType, core.class.Space)
|
||||
export class TProjectType extends TSpace implements ProjectType {
|
||||
statuses!: ProjectStatus[]
|
||||
shortDescription?: string
|
||||
category!: Ref<ProjectTypeCategory>
|
||||
|
||||
@Prop(TypeRef(task.class.ProjectTypeDescriptor), getEmbeddedLabel('Descriptor'))
|
||||
descriptor!: Ref<ProjectTypeDescriptor>
|
||||
|
||||
@Prop(ArrOf(TypeRef(task.class.TaskType)), getEmbeddedLabel('Tasks'))
|
||||
tasks!: Ref<TaskType>[]
|
||||
|
||||
@Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Project statuses'))
|
||||
statuses!: ProjectStatus[]
|
||||
|
||||
@Prop(TypeRef(core.class.Class), getEmbeddedLabel('Target Class'))
|
||||
targetClass!: Ref<Class<Project>>
|
||||
}
|
||||
|
||||
@Model(task.class.ProjectTypeCategory, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TProjectTypeCategory extends TDoc implements ProjectTypeCategory {
|
||||
@Model(task.class.TaskType, core.class.Doc, DOMAIN_TASK)
|
||||
export class TTaskType extends TDoc implements TaskType {
|
||||
@Prop(TypeString(), getEmbeddedLabel('Name'))
|
||||
name!: string
|
||||
|
||||
@Prop(TypeRef(task.class.TaskTypeDescriptor), getEmbeddedLabel('Descriptor'))
|
||||
descriptor!: Ref<TaskTypeDescriptor>
|
||||
|
||||
@Prop(TypeRef(task.class.ProjectType), getEmbeddedLabel('Task class'))
|
||||
parent!: Ref<ProjectType> // Base class for task
|
||||
|
||||
@Prop(TypeString(), getEmbeddedLabel('Kind'))
|
||||
kind!: TaskTypeKind
|
||||
|
||||
@Prop(ArrOf(TypeRef(task.class.TaskType)), getEmbeddedLabel('Parent'))
|
||||
allowedAsChildOf!: Ref<TaskType>[] // In case of specified, task type is for sub-tasks
|
||||
|
||||
@Prop(TypeRef(core.class.Class), getEmbeddedLabel('Task class'))
|
||||
ofClass!: Ref<Class<Task>> // Base class for task
|
||||
|
||||
@Prop(TypeRef(core.class.Class), getEmbeddedLabel('Task target class'))
|
||||
targetClass!: Ref<Class<Task>> // Class or Mixin mixin to hold all user defined attributes.
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.Status)), getEmbeddedLabel('Task statuses'))
|
||||
statuses!: Ref<Status>[]
|
||||
|
||||
@Prop(TypeRef(core.class.Class), getEmbeddedLabel('Task status class'))
|
||||
statusClass!: Ref<Class<Status>>
|
||||
|
||||
@Prop(TypeRef(core.class.StatusCategory), getEmbeddedLabel('Task status categories'))
|
||||
statusCategories!: Ref<StatusCategory>[]
|
||||
}
|
||||
|
||||
@Model(task.class.ProjectTypeDescriptor, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TProjectTypeDescriptor extends TDoc implements ProjectTypeDescriptor {
|
||||
name!: IntlString
|
||||
description!: IntlString
|
||||
icon!: AnyComponent
|
||||
editor?: AnyComponent
|
||||
attachedToClass!: Ref<Class<Project>>
|
||||
statusClass!: Ref<Class<Status>>
|
||||
statusCategories!: Ref<StatusCategory>[]
|
||||
baseClass!: Ref<Class<Task>>
|
||||
}
|
||||
|
||||
@Model(task.class.Sequence, core.class.Doc, DOMAIN_KANBAN)
|
||||
@ -218,7 +309,19 @@ export const actionTemplates = template({
|
||||
})
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TKanbanCard, TSequence, TTask, TTodoItem, TProject, TProjectType, TProjectTypeCategory)
|
||||
builder.createModel(
|
||||
TKanbanCard,
|
||||
TSequence,
|
||||
TTask,
|
||||
TTodoItem,
|
||||
TProject,
|
||||
TProjectType,
|
||||
TTaskType,
|
||||
TProjectTypeDescriptor,
|
||||
TTaskTypeDescriptor,
|
||||
TTaskTypeClass,
|
||||
TProjectTypeClass
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
@ -243,6 +346,10 @@ export function createModel (builder: Builder): void {
|
||||
editor: task.component.TaskHeader
|
||||
})
|
||||
|
||||
builder.mixin(task.class.ProjectType, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Open]
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
@ -312,6 +419,16 @@ export function createModel (builder: Builder): void {
|
||||
presenter: task.component.TodoItemPresenter
|
||||
})
|
||||
|
||||
builder.mixin(task.class.TaskType, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: task.component.TaskTypePresenter
|
||||
})
|
||||
|
||||
builder.mixin(task.class.ProjectType, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: task.component.ProjectTypePresenter
|
||||
})
|
||||
|
||||
classPresenter(builder, task.class.TaskType, task.component.TaskTypePresenter, task.component.TaskTypePresenter)
|
||||
|
||||
createAction(builder, {
|
||||
label: task.string.MarkAsDone,
|
||||
icon: task.icon.TodoCheck,
|
||||
@ -370,7 +487,21 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
ofAttribute: task.attribute.State,
|
||||
label: core.string.Status,
|
||||
label: task.string.StateBacklog,
|
||||
icon: task.icon.TaskState,
|
||||
color: PaletteColorIndexes.Coin,
|
||||
defaultStatusName: 'Backlog',
|
||||
order: 0
|
||||
},
|
||||
task.statusCategory.UnStarted
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.StatusCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
ofAttribute: task.attribute.State,
|
||||
label: task.string.StateActive,
|
||||
icon: task.icon.TaskState,
|
||||
color: PaletteColorIndexes.Blueberry,
|
||||
defaultStatusName: 'New state',
|
||||
@ -431,4 +562,252 @@ export function createModel (builder: Builder): void {
|
||||
// },
|
||||
// task.ids.AssigneedNotification
|
||||
// )
|
||||
|
||||
builder.mixin(task.mixin.TaskTypeClass, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: task.component.TaskTypeClassPresenter
|
||||
})
|
||||
builder.mixin(task.mixin.ProjectTypeClass, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: task.component.ProjectTypeClassPresenter
|
||||
})
|
||||
|
||||
builder.mixin(task.class.Task, core.class.Class, setting.mixin.Editable, {
|
||||
value: true
|
||||
})
|
||||
|
||||
builder.mixin(task.class.Project, core.class.Class, setting.mixin.Editable, {
|
||||
value: true
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.SettingsCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'statuses',
|
||||
label: task.string.ManageProjects,
|
||||
icon: task.icon.ManageTemplates,
|
||||
component: task.component.ManageProjectsContent,
|
||||
extraComponents: {
|
||||
navigation: task.component.ManageProjects,
|
||||
tools: task.component.ManageProjectsTools
|
||||
},
|
||||
group: 'settings-editor',
|
||||
secured: false,
|
||||
order: 6000,
|
||||
expandable: true
|
||||
},
|
||||
task.ids.ManageProjects
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type FixTaskData = Omit<Data<TaskType>, 'space' | 'statuses' | 'parent'> & { _id?: TaskType['_id'] }
|
||||
export interface FixTaskResult {
|
||||
taskTypes: TaskType[]
|
||||
projectTypes: ProjectType[]
|
||||
projects: Project[]
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function fixTaskTypes (
|
||||
client: MigrationClient,
|
||||
descriptor: Ref<ProjectTypeDescriptor>,
|
||||
dataFactory: (t: ProjectType) => Promise<FixTaskData[]>
|
||||
): Promise<FixTaskResult> {
|
||||
const categoryObj = client.model.findObject(descriptor)
|
||||
if (categoryObj === undefined) {
|
||||
throw new Error('category is not found in model')
|
||||
}
|
||||
|
||||
const projectTypes = await client.find<ProjectType>(DOMAIN_SPACE, {
|
||||
_class: task.class.ProjectType,
|
||||
descriptor
|
||||
})
|
||||
const baseClassClass = client.hierarchy.getClass(categoryObj.baseClass)
|
||||
|
||||
const resultTaskTypes: TaskType[] = []
|
||||
const resultProjects: Project[] = []
|
||||
|
||||
for (const t of projectTypes) {
|
||||
t.tasks = [...(t.tasks ?? [])]
|
||||
if (t.targetClass === undefined) {
|
||||
const targetProjectClassId: Ref<Class<Doc>> = generateId()
|
||||
t.targetClass = targetProjectClassId
|
||||
|
||||
await client.create<TxCreateDoc<Doc>>(DOMAIN_TX, {
|
||||
_id: generateId(),
|
||||
objectId: targetProjectClassId,
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: core.class.Class,
|
||||
objectSpace: core.space.Model,
|
||||
modifiedBy: core.account.ConfigUser,
|
||||
modifiedOn: Date.now(),
|
||||
space: core.space.Model,
|
||||
attributes: {
|
||||
extends: categoryObj.baseClass,
|
||||
kind: ClassifierKind.MIXIN,
|
||||
label: baseClassClass.label,
|
||||
icon: baseClassClass.icon
|
||||
}
|
||||
})
|
||||
|
||||
await client.create<TxMixin<Class<ProjectType>, ProjectTypeClass>>(DOMAIN_TX, {
|
||||
_class: core.class.TxMixin,
|
||||
_id: generateId(),
|
||||
space: core.space.Model,
|
||||
modifiedBy: core.account.ConfigUser,
|
||||
modifiedOn: Date.now(),
|
||||
objectId: targetProjectClassId,
|
||||
objectClass: core.class.Class,
|
||||
objectSpace: core.space.Model,
|
||||
mixin: task.mixin.ProjectTypeClass,
|
||||
attributes: {
|
||||
projectType: t._id
|
||||
}
|
||||
})
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{
|
||||
_id: t._id
|
||||
},
|
||||
{ $set: { targetClass: targetProjectClassId } }
|
||||
)
|
||||
}
|
||||
|
||||
const dataTypes = await dataFactory(t)
|
||||
|
||||
const projects = await client.find<Project>(DOMAIN_SPACE, { type: t._id })
|
||||
resultProjects.push(...projects)
|
||||
|
||||
for (const data of dataTypes) {
|
||||
const taskTypeId: Ref<TaskType> = data._id ?? generateId()
|
||||
const descr = client.model.getObject(data.descriptor)
|
||||
|
||||
const statuses = await client.find<Status>(DOMAIN_STATUS, {
|
||||
_id: { $in: t.statuses.map((it) => it._id) },
|
||||
_class: data.statusClass
|
||||
})
|
||||
|
||||
const dStatuses = [...t.statuses.map((it) => it._id)]
|
||||
const statusAttr = findStatusAttr(client.hierarchy, data.ofClass)
|
||||
// Ensure we have at leas't one item in every category.
|
||||
for (const c of data.statusCategories) {
|
||||
const cat = await client.model.findOne(core.class.StatusCategory, { _id: c })
|
||||
const st = statuses.find((it) => it.category === c)
|
||||
if (st === undefined) {
|
||||
// We need to add new status into missing category
|
||||
const statusId: Ref<Status> = generateId()
|
||||
await client.create<Status>(DOMAIN_STATUS, {
|
||||
_id: statusId,
|
||||
_class: data.statusClass,
|
||||
category: c,
|
||||
modifiedBy: core.account.ConfigUser,
|
||||
modifiedOn: Date.now(),
|
||||
name: cat?.defaultStatusName ?? 'New state',
|
||||
space: task.space.Statuses,
|
||||
ofAttribute: statusAttr._id
|
||||
})
|
||||
dStatuses.push(statusId)
|
||||
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{
|
||||
_id: t._id
|
||||
},
|
||||
{ $push: { statuses: { _id: statusId } } }
|
||||
)
|
||||
t.statuses.push({ _id: statusId, taskType: taskTypeId })
|
||||
}
|
||||
}
|
||||
const taskType: TaskType = {
|
||||
...data,
|
||||
parent: t._id,
|
||||
_id: taskTypeId,
|
||||
_class: task.class.TaskType,
|
||||
space: t._id,
|
||||
statuses: dStatuses,
|
||||
modifiedBy: core.account.System,
|
||||
modifiedOn: Date.now(),
|
||||
kind: 'both',
|
||||
icon: data.icon ?? descr.icon
|
||||
}
|
||||
|
||||
const ofClassClass = client.hierarchy.getClass(data.ofClass)
|
||||
|
||||
taskType.icon = ofClassClass.icon
|
||||
|
||||
// Create target class for custom field.
|
||||
const targetClassId: Ref<Class<Doc>> = generateId()
|
||||
taskType.targetClass = targetClassId
|
||||
|
||||
await client.create<TxCreateDoc<Doc>>(DOMAIN_TX, {
|
||||
_id: generateId(),
|
||||
objectId: targetClassId,
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: core.class.Class,
|
||||
objectSpace: core.space.Model,
|
||||
modifiedBy: core.account.ConfigUser,
|
||||
modifiedOn: Date.now(),
|
||||
space: core.space.Model,
|
||||
attributes: {
|
||||
extends: data.ofClass,
|
||||
kind: ClassifierKind.MIXIN,
|
||||
label: getEmbeddedLabel(data.name),
|
||||
icon: ofClassClass.icon
|
||||
}
|
||||
})
|
||||
|
||||
await client.create<TxMixin<Class<TaskType>, TaskTypeClass>>(DOMAIN_TX, {
|
||||
_class: core.class.TxMixin,
|
||||
_id: generateId(),
|
||||
space: core.space.Model,
|
||||
modifiedBy: core.account.ConfigUser,
|
||||
modifiedOn: Date.now(),
|
||||
objectId: targetClassId,
|
||||
objectClass: core.class.Class,
|
||||
objectSpace: core.space.Model,
|
||||
mixin: task.mixin.TaskTypeClass,
|
||||
attributes: {
|
||||
taskType: taskTypeId,
|
||||
projectType: t._id
|
||||
}
|
||||
})
|
||||
|
||||
await client.create(DOMAIN_TASK, taskType)
|
||||
resultTaskTypes.push(taskType)
|
||||
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{
|
||||
_id: t._id
|
||||
},
|
||||
{ $push: { tasks: taskTypeId } }
|
||||
)
|
||||
t.tasks.push(taskTypeId)
|
||||
// Update kind and target classId
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ space: { $in: projects.map((it) => it._id) }, _class: data.ofClass },
|
||||
{ $set: { kind: taskTypeId } }
|
||||
)
|
||||
}
|
||||
|
||||
// We need to fix project statuses field, for proper icon calculation.
|
||||
}
|
||||
for (const t of projectTypes) {
|
||||
const ttypes = await client.find<TaskType>(DOMAIN_TASK, { _id: { $in: t.tasks } })
|
||||
const newStatuses = calculateStatuses(t, new Map(ttypes.map((it) => [it._id, it])), [])
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{ _id: t._id },
|
||||
{
|
||||
$set: {
|
||||
statuses: newStatuses
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
return { taskTypes: resultTaskTypes, projectTypes, projects: resultProjects }
|
||||
}
|
||||
|
@ -13,46 +13,20 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { TxOperations, type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import {
|
||||
type Attribute,
|
||||
type Class,
|
||||
DOMAIN_STATUS,
|
||||
DOMAIN_TX,
|
||||
type Data,
|
||||
type Doc,
|
||||
type Ref,
|
||||
type Space,
|
||||
type Status,
|
||||
TxOperations,
|
||||
generateId,
|
||||
toIdMap
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
createOrUpdate,
|
||||
tryMigrate,
|
||||
tryUpgrade
|
||||
tryUpgrade,
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
import tags from '@hcengineering/model-tags'
|
||||
import {
|
||||
type Project,
|
||||
type ProjectStatus,
|
||||
type ProjectType,
|
||||
type ProjectTypeCategory,
|
||||
type Task,
|
||||
createState,
|
||||
taskId
|
||||
} from '@hcengineering/task'
|
||||
import view, { type Filter } from '@hcengineering/view'
|
||||
import { DOMAIN_KANBAN, DOMAIN_TASK } from '.'
|
||||
import { taskId } from '@hcengineering/task'
|
||||
import task from './plugin'
|
||||
|
||||
type ProjectData = Omit<Data<ProjectType>, 'statuses' | 'private' | 'members' | 'archived'>
|
||||
type OldStatus = Status & { rank: string }
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -65,47 +39,6 @@ export async function createSequence (tx: TxOperations, _class: Ref<Class<Doc>>)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createProjectType (
|
||||
client: TxOperations,
|
||||
data: ProjectData,
|
||||
states: Data<Status>[],
|
||||
_id: Ref<ProjectType>,
|
||||
stateClass: Ref<Class<Status>> = core.class.Status
|
||||
): Promise<Ref<ProjectType>> {
|
||||
const current = await client.findOne(task.class.ProjectType, { _id })
|
||||
if (current !== undefined) {
|
||||
return current._id
|
||||
}
|
||||
|
||||
const statuses: Ref<Status>[] = []
|
||||
for (const st of states) {
|
||||
statuses.push(await createState(client, stateClass, st))
|
||||
}
|
||||
|
||||
const tmpl = await client.createDoc(
|
||||
task.class.ProjectType,
|
||||
core.space.Model,
|
||||
{
|
||||
description: data.description,
|
||||
shortDescription: data.shortDescription,
|
||||
category: data.category,
|
||||
statuses: statuses.map((p) => {
|
||||
return { _id: p }
|
||||
}),
|
||||
name: data.name,
|
||||
private: false,
|
||||
members: [],
|
||||
archived: false
|
||||
},
|
||||
_id
|
||||
)
|
||||
|
||||
return tmpl
|
||||
}
|
||||
|
||||
async function createDefaultSequence (tx: TxOperations): Promise<void> {
|
||||
const current = await tx.findOne(core.class.Space, {
|
||||
_id: task.space.Sequence
|
||||
@ -151,469 +84,9 @@ async function createDefaults (tx: TxOperations): Promise<void> {
|
||||
await createDefaultStatesSpace(tx)
|
||||
}
|
||||
|
||||
async function renameState (client: MigrationClient): Promise<void> {
|
||||
const toUpdate = await client.find(DOMAIN_TASK, { state: { $exists: true } })
|
||||
if (toUpdate.length > 0) {
|
||||
for (const doc of toUpdate) {
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ objectId: doc._id },
|
||||
{ $rename: { 'attributes.state': 'attributes.status', 'operations.state': 'operations.status' } }
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ 'tx.objectId': doc._id },
|
||||
{ $rename: { 'tx.attributes.state': 'tx.attributes.status', 'tx.operations.state': 'tx.operations.status' } }
|
||||
)
|
||||
}
|
||||
await client.update(DOMAIN_TASK, { _id: { $in: toUpdate.map((p) => p._id) } }, { $rename: { state: 'status' } })
|
||||
}
|
||||
}
|
||||
|
||||
async function renameStatePrefs (client: MigrationUpgradeClient): Promise<void> {
|
||||
const txop = new TxOperations(client, core.account.System)
|
||||
const prefs = await client.findAll(view.class.ViewletPreference, {})
|
||||
for (const pref of prefs) {
|
||||
let update = false
|
||||
const config = pref.config
|
||||
for (let index = 0; index < config.length; index++) {
|
||||
const conf = config[index]
|
||||
if (typeof conf === 'string') {
|
||||
if (conf === 'state') {
|
||||
config[index] = 'status'
|
||||
update = true
|
||||
} else if (conf === '$lookup.state') {
|
||||
config[index] = '$lookup.status'
|
||||
update = true
|
||||
}
|
||||
} else if (conf.key === 'state') {
|
||||
conf.key = 'status'
|
||||
update = true
|
||||
}
|
||||
}
|
||||
if (update) {
|
||||
await txop.update(pref, {
|
||||
config
|
||||
})
|
||||
}
|
||||
}
|
||||
const res = await client.findAll(view.class.FilteredView, { filters: /"key":"state"/ as any })
|
||||
if (res.length > 0) {
|
||||
for (const doc of res) {
|
||||
const filters = JSON.parse(doc.filters) as Filter[]
|
||||
for (const filter of filters) {
|
||||
if (filter.key.key === 'state') {
|
||||
filter.key.key = 'status'
|
||||
}
|
||||
}
|
||||
await txop.update(doc, {
|
||||
filters: JSON.stringify(filters)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fixStatusAttributes (client: MigrationClient): Promise<void> {
|
||||
const spaces = await client.find<Space>(DOMAIN_SPACE, {})
|
||||
const map = toIdMap(spaces)
|
||||
const oldStatuses = await client.find<OldStatus>(DOMAIN_STATUS, { ofAttribute: { $exists: false } })
|
||||
for (const oldStatus of oldStatuses) {
|
||||
const space = map.get(oldStatus.space)
|
||||
if (space !== undefined) {
|
||||
try {
|
||||
let ofAttribute = task.attribute.State
|
||||
if (space._class === ('recruit:class:Vacancy' as Ref<Class<Space>>)) {
|
||||
ofAttribute = 'recruit:attribute:State' as Ref<Attribute<Status>>
|
||||
}
|
||||
if (space._class === ('lead:class:Funnel' as Ref<Class<Space>>)) {
|
||||
ofAttribute = 'lead:attribute:State' as Ref<Attribute<Status>>
|
||||
}
|
||||
if (space._class === ('board:class:Board' as Ref<Class<Space>>)) {
|
||||
ofAttribute = 'board:attribute:State' as Ref<Attribute<Status>>
|
||||
}
|
||||
if (space._class === ('tracker:class:Project' as Ref<Class<Space>>)) {
|
||||
ofAttribute = 'tracker:attribute:IssueStatus' as Ref<Attribute<Status>>
|
||||
}
|
||||
if (ofAttribute !== oldStatus.ofAttribute) {
|
||||
await client.update(DOMAIN_STATUS, { _id: oldStatus._id }, { ofAttribute })
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTemplateOfAttribute (space: Ref<Space>): Ref<Attribute<Status>> {
|
||||
let ofAttribute = task.attribute.State
|
||||
if (space === ('recruit:space:VacancyTemplates' as Ref<Space>)) {
|
||||
ofAttribute = 'recruit:attribute:State' as Ref<Attribute<Status>>
|
||||
}
|
||||
if (space === ('lead:space:FunnelTemplates' as Ref<Space>)) {
|
||||
ofAttribute = 'lead:attribute:State' as Ref<Attribute<Status>>
|
||||
}
|
||||
if (space === ('board:space:BoardTemplates' as Ref<Space>)) {
|
||||
ofAttribute = 'board:attribute:State' as Ref<Attribute<Status>>
|
||||
}
|
||||
return ofAttribute
|
||||
}
|
||||
|
||||
async function fixStatusDoneAttributes (client: MigrationClient): Promise<void> {
|
||||
const oldStatuses = await client.find<OldStatus>(DOMAIN_STATUS, {})
|
||||
for (const oldStatus of oldStatuses) {
|
||||
if (!oldStatus.ofAttribute.includes('DoneState')) continue
|
||||
const ofAttribute = oldStatus.ofAttribute.replace('DoneState', 'State')
|
||||
await client.update(DOMAIN_STATUS, { _id: oldStatus._id }, { ofAttribute })
|
||||
}
|
||||
}
|
||||
|
||||
async function removeDoneStatuses (client: MigrationClient): Promise<void> {
|
||||
const tasks = await client.find<Task>(DOMAIN_TASK, { doneState: { $exists: true } })
|
||||
for (const task of tasks) {
|
||||
if ((task as any).doneState != null) {
|
||||
await client.update(DOMAIN_TASK, { _id: task._id }, { status: (task as any).doneState, isDone: true })
|
||||
}
|
||||
await client.update(DOMAIN_TASK, { _id: task._id }, { $unset: { doneState: '' } })
|
||||
}
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ 'tx.operations.doneState': { $ne: null } },
|
||||
{ $rename: { 'tx.operations.doneState': 'tx.operations.status' } }
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ 'tx.attributes.doneState': { $ne: null } },
|
||||
{ $rename: { 'tx.attributes.doneState': 'tx.attributes.status' } }
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ 'tx.operations.doneState': { $exists: true } },
|
||||
{ $unset: { 'tx.operations.doneState': '' } }
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ 'tx.attributes.doneState': { $exists: true } },
|
||||
{ $unset: { 'tx.attributes.doneState': '' } }
|
||||
)
|
||||
|
||||
// we need join doneStates to states for all projects
|
||||
const classes = client.hierarchy.getDescendants(task.class.Project)
|
||||
const projects = await client.find<Project>(DOMAIN_SPACE, { _class: { $in: classes }, doneStates: { $exists: true } })
|
||||
for (const project of projects) {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{ _id: project._id },
|
||||
{ states: (project as any).states.concat((project as any).doneStates) }
|
||||
)
|
||||
await client.update(DOMAIN_SPACE, { _id: project._id }, { $unset: { doneStates: '' } })
|
||||
}
|
||||
}
|
||||
|
||||
async function removeStateClass (client: MigrationClient): Promise<void> {
|
||||
await client.update<Status>(
|
||||
DOMAIN_STATUS,
|
||||
{ _class: 'task:class:State' as Ref<Class<Doc>> },
|
||||
{ _class: core.class.Status, category: task.statusCategory.Active }
|
||||
)
|
||||
await client.update(DOMAIN_TX, { objectClass: 'task:class:State' }, { objectClass: core.class.Status })
|
||||
await client.update(
|
||||
DOMAIN_STATUS,
|
||||
{ _class: 'task:class:WonState' as Ref<Class<Doc>> },
|
||||
{ _class: core.class.Status, category: task.statusCategory.Won }
|
||||
)
|
||||
await client.update(DOMAIN_TX, { objectClass: 'task:class:WonState' }, { objectClass: core.class.Status })
|
||||
await client.update(
|
||||
DOMAIN_STATUS,
|
||||
{ _class: 'task:class:LostState' as Ref<Class<Doc>> },
|
||||
{ _class: core.class.Status, category: task.statusCategory.Lost }
|
||||
)
|
||||
await client.update(DOMAIN_TX, { objectClass: 'task:class:LostState' }, { objectClass: core.class.Status })
|
||||
await client.update(
|
||||
DOMAIN_STATUS,
|
||||
{ _class: 'task:class:DoneState' as Ref<Class<Doc>> },
|
||||
{ _class: core.class.Status }
|
||||
)
|
||||
await client.update(DOMAIN_TX, { objectClass: 'task:class:DoneState' }, { objectClass: core.class.Status })
|
||||
}
|
||||
|
||||
async function migrateTemplatesToTypes (client: MigrationClient): Promise<void> {
|
||||
interface KanbanTemplate extends Doc {
|
||||
title: string
|
||||
description?: string
|
||||
shortDescription?: string
|
||||
}
|
||||
|
||||
interface KanbanTemplateSpace extends Space {
|
||||
attachedToClass: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
interface StateTemplate extends Doc, Status {
|
||||
attachedTo: Ref<KanbanTemplate>
|
||||
rank: string
|
||||
}
|
||||
|
||||
const classes = client.hierarchy.getDescendants(task.class.Project)
|
||||
const templates = await client.find<KanbanTemplate>(DOMAIN_KANBAN, {
|
||||
_class: 'task:class:KanbanTemplate' as Ref<Class<Doc>>
|
||||
})
|
||||
for (const template of templates) {
|
||||
const used = await client.find(DOMAIN_SPACE, { templateId: template._id })
|
||||
if (used.length === 0) {
|
||||
await client.delete(DOMAIN_KANBAN, template._id)
|
||||
continue
|
||||
}
|
||||
const space = (
|
||||
await client.find<KanbanTemplateSpace>(DOMAIN_SPACE, { _id: template.space as Ref<KanbanTemplateSpace> })
|
||||
)[0]
|
||||
if (space === undefined) continue
|
||||
const states = await client.find<StateTemplate>(
|
||||
DOMAIN_KANBAN,
|
||||
{ _class: 'task:class:StateTemplate' as Ref<Class<Doc>>, attachedTo: template._id },
|
||||
{ sort: { rank: 1 } }
|
||||
)
|
||||
const wonStates = await client.find<StateTemplate>(
|
||||
DOMAIN_KANBAN,
|
||||
{ _class: 'task:class:WonStateTemplate' as Ref<Class<Doc>>, attachedTo: template._id },
|
||||
{ sort: { rank: 1 } }
|
||||
)
|
||||
const lostStates = await client.find<StateTemplate>(
|
||||
DOMAIN_KANBAN,
|
||||
{ _class: 'task:class:LostStateTemplate' as Ref<Class<Doc>>, attachedTo: template._id },
|
||||
{ sort: { rank: 1 } }
|
||||
)
|
||||
|
||||
const statuses: ProjectStatus[] = []
|
||||
const currentStates = await client.find<Status>(DOMAIN_STATUS, {})
|
||||
for (const st of states) {
|
||||
const exists = currentStates.find((p) => p.name.toLocaleLowerCase() === st.name.toLocaleLowerCase())
|
||||
if (exists !== undefined) {
|
||||
statuses.push({ _id: exists._id, color: st.color })
|
||||
} else {
|
||||
const id = generateId<Status>()
|
||||
await client.create<Status>(DOMAIN_STATUS, {
|
||||
ofAttribute: getTemplateOfAttribute(st.space),
|
||||
name: st.name,
|
||||
_id: id,
|
||||
space: task.space.Statuses,
|
||||
modifiedOn: st.modifiedOn,
|
||||
modifiedBy: st.modifiedBy,
|
||||
_class: core.class.Status,
|
||||
color: st.color ?? 9,
|
||||
createdBy: st.createdBy,
|
||||
createdOn: st.createdOn,
|
||||
category: task.statusCategory.Active
|
||||
})
|
||||
statuses.push({ _id: id, color: st.color })
|
||||
}
|
||||
}
|
||||
|
||||
for (const st of wonStates) {
|
||||
const exists = currentStates.find((p) => p.name.toLocaleLowerCase() === st.name.toLocaleLowerCase())
|
||||
if (exists !== undefined) {
|
||||
statuses.push({ _id: exists._id, color: st.color })
|
||||
} else {
|
||||
const id = generateId<Status>()
|
||||
await client.create<Status>(DOMAIN_STATUS, {
|
||||
ofAttribute: getTemplateOfAttribute(st.space),
|
||||
name: st.name,
|
||||
_id: id,
|
||||
space: task.space.Statuses,
|
||||
modifiedOn: st.modifiedOn,
|
||||
modifiedBy: st.modifiedBy,
|
||||
_class: core.class.Status,
|
||||
color: st.color ?? 15,
|
||||
createdBy: st.createdBy,
|
||||
createdOn: st.createdOn,
|
||||
category: task.statusCategory.Won
|
||||
})
|
||||
statuses.push({ _id: id, color: st.color })
|
||||
}
|
||||
}
|
||||
|
||||
for (const st of lostStates) {
|
||||
const exists = currentStates.find((p) => p.name.toLocaleLowerCase() === st.name.toLocaleLowerCase())
|
||||
if (exists !== undefined) {
|
||||
statuses.push({ _id: exists._id, color: st.color })
|
||||
} else {
|
||||
const id = generateId<Status>()
|
||||
await client.create<Status>(DOMAIN_STATUS, {
|
||||
ofAttribute: getTemplateOfAttribute(st.space),
|
||||
name: st.name,
|
||||
_id: id,
|
||||
space: task.space.Statuses,
|
||||
modifiedOn: st.modifiedOn,
|
||||
modifiedBy: st.modifiedBy,
|
||||
_class: core.class.Status,
|
||||
color: st.color ?? 0,
|
||||
createdBy: st.createdBy,
|
||||
createdOn: st.createdOn,
|
||||
category: task.statusCategory.Lost
|
||||
})
|
||||
statuses.push({ _id: id, color: st.color })
|
||||
}
|
||||
}
|
||||
|
||||
const category = await getProjectTypeCategory(client, space.attachedToClass)
|
||||
await client.create<ProjectType>(DOMAIN_SPACE, {
|
||||
name: template.title,
|
||||
description: template.description ?? '',
|
||||
shortDescription: template.shortDescription,
|
||||
category: category ?? (template.space as Ref<Doc> as Ref<ProjectTypeCategory>),
|
||||
private: false,
|
||||
members: [],
|
||||
archived: false,
|
||||
_id: template._id as Ref<Doc> as Ref<ProjectType>,
|
||||
space: core.space.Space,
|
||||
modifiedOn: template.modifiedOn,
|
||||
modifiedBy: template.modifiedBy,
|
||||
_class: task.class.ProjectType,
|
||||
statuses
|
||||
})
|
||||
await client.delete(DOMAIN_KANBAN, template._id)
|
||||
}
|
||||
|
||||
// we should found all projects without types and has templateID we should just rename it to type (if it exists)
|
||||
const projectsWithTemplate = await client.find<Project>(DOMAIN_SPACE, {
|
||||
_class: { $in: classes },
|
||||
type: { $exists: false },
|
||||
templateId: { $exists: true }
|
||||
})
|
||||
for (const project of projectsWithTemplate) {
|
||||
await client.update(DOMAIN_SPACE, { _id: project._id }, { type: (project as any).templateId })
|
||||
}
|
||||
|
||||
// we should remove all state templates
|
||||
const stateClasses = [
|
||||
'task:class:StateTemplate' as Ref<Class<Doc>>,
|
||||
'task:class:WonStateTemplate' as Ref<Class<Doc>>,
|
||||
'task:class:LostStateTemplate' as Ref<Class<Doc>>
|
||||
]
|
||||
const states = await client.find(DOMAIN_KANBAN, { _class: { $in: stateClasses } })
|
||||
for (const st of states) {
|
||||
await client.delete(DOMAIN_KANBAN, st._id)
|
||||
}
|
||||
}
|
||||
|
||||
async function getProjectTypeCategory (
|
||||
client: MigrationClient,
|
||||
_class: Ref<Class<Project>>
|
||||
): Promise<Ref<ProjectTypeCategory> | undefined> {
|
||||
let clazz = _class
|
||||
while (true) {
|
||||
const res = await client.model.findAll(task.class.ProjectTypeCategory, { attachedToClass: clazz })
|
||||
if (res[0] !== undefined) return res[0]._id
|
||||
const parent = client.hierarchy.getClass(clazz)
|
||||
if (parent.extends === undefined) return
|
||||
clazz = parent.extends
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateProjectTypes (client: MigrationClient): Promise<void> {
|
||||
const classes = client.hierarchy.getDescendants(task.class.Project)
|
||||
// we should found all projects without types and group by class and then by statuses and create new type
|
||||
const projects = await client.find<Project>(DOMAIN_SPACE, { _class: { $in: classes }, type: { $exists: false } })
|
||||
const projectsByCategory = new Map<Ref<ProjectTypeCategory>, Project[]>()
|
||||
for (const project of projects) {
|
||||
const category = await getProjectTypeCategory(client, project._class)
|
||||
if (category === undefined) continue
|
||||
const arr = projectsByCategory.get(category) ?? []
|
||||
arr.push(project)
|
||||
projectsByCategory.set(category, arr)
|
||||
}
|
||||
for (const [category, projects] of projectsByCategory) {
|
||||
const gouped = group(projects)
|
||||
for (const gr of gouped) {
|
||||
await client.create<ProjectType>(DOMAIN_SPACE, {
|
||||
category,
|
||||
name: gr.projects[0].name,
|
||||
description: '',
|
||||
private: false,
|
||||
members: [],
|
||||
archived: false,
|
||||
_id: gr.type,
|
||||
space: core.space.Space,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: core.account.System,
|
||||
_class: task.class.ProjectType,
|
||||
statuses: gr.statuses.map((p) => {
|
||||
return { _id: p }
|
||||
})
|
||||
})
|
||||
|
||||
await client.update(DOMAIN_SPACE, { _id: { $in: gr.projects.map((p) => p._id) } }, { type: gr.type })
|
||||
}
|
||||
}
|
||||
|
||||
// we need remove states from all projects
|
||||
const allProjects = await client.find<Project>(DOMAIN_SPACE, { _class: { $in: classes } })
|
||||
await Promise.all(
|
||||
allProjects.map(async (project) => {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{ _id: project._id },
|
||||
{ $unset: { states: '', templateId: '', doneStates: '' } }
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
interface ProjectTypeGroup {
|
||||
statuses: Ref<Status>[]
|
||||
projects: Project[]
|
||||
type: Ref<ProjectType>
|
||||
}
|
||||
|
||||
function group (projects: Project[]): ProjectTypeGroup[] {
|
||||
const map = new Map<string, ProjectTypeGroup>()
|
||||
|
||||
for (const project of projects) {
|
||||
const ids = getIds((project as any).states ?? [])
|
||||
const obj: ProjectTypeGroup = map.get(ids) ?? {
|
||||
statuses: (project as any).states ?? [],
|
||||
projects: [],
|
||||
type: generateId<ProjectType>()
|
||||
}
|
||||
obj.projects.push(project)
|
||||
map.set(ids, obj)
|
||||
}
|
||||
return Array.from(map.values())
|
||||
}
|
||||
|
||||
function getIds (states: Ref<Status>[]): string {
|
||||
return states.join(',')
|
||||
}
|
||||
|
||||
export const taskOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, taskId, [
|
||||
{
|
||||
state: 'fixStatusAttributes',
|
||||
func: fixStatusAttributes
|
||||
},
|
||||
{
|
||||
state: 'renameState',
|
||||
func: renameState
|
||||
},
|
||||
{
|
||||
state: 'removeDoneStatuses',
|
||||
func: removeDoneStatuses
|
||||
},
|
||||
{
|
||||
state: 'fixStatusDoneAttributes',
|
||||
func: fixStatusDoneAttributes
|
||||
},
|
||||
{
|
||||
state: 'removeStateClass',
|
||||
func: removeStateClass
|
||||
},
|
||||
{
|
||||
state: 'migrateTemplatesToTypes',
|
||||
func: migrateTemplatesToTypes
|
||||
},
|
||||
{
|
||||
state: 'migrateProjectTypes',
|
||||
func: migrateProjectTypes
|
||||
},
|
||||
{
|
||||
state: 'projectTypeSpace',
|
||||
func: async (client) => {
|
||||
@ -640,11 +113,6 @@ export const taskOperation: MigrateOperation = {
|
||||
task.category.TaskTag
|
||||
)
|
||||
|
||||
await tryUpgrade(client, taskId, [
|
||||
{
|
||||
state: 'renameStatePrefs',
|
||||
func: renameStatePrefs
|
||||
}
|
||||
])
|
||||
await tryUpgrade(client, taskId, [])
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {} from '@hcengineering/notification'
|
||||
import type { Ref, Space } from '@hcengineering/core'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { mergeIds, type IntlString } from '@hcengineering/platform'
|
||||
import { type TagCategory } from '@hcengineering/tags'
|
||||
import { taskId } from '@hcengineering/task'
|
||||
import task from '@hcengineering/task-resources/src/plugin'
|
||||
@ -59,7 +60,14 @@ export default mergeIds(taskId, task, {
|
||||
StatusSelector: '' as AnyComponent,
|
||||
TemplatesIcon: '' as AnyComponent,
|
||||
TypesView: '' as AnyComponent,
|
||||
StateIconPresenter: '' as AnyComponent
|
||||
StateIconPresenter: '' as AnyComponent,
|
||||
TaskTypePresenter: '' as AnyComponent,
|
||||
ProjectTypePresenter: '' as AnyComponent,
|
||||
TaskTypeClassPresenter: '' as AnyComponent,
|
||||
ProjectTypeClassPresenter: '' as AnyComponent,
|
||||
ManageProjects: '' as AnyComponent,
|
||||
ManageProjectsTools: '' as AnyComponent,
|
||||
ManageProjectsContent: '' as AnyComponent
|
||||
},
|
||||
space: {
|
||||
TasksPublic: '' as Ref<Space>
|
||||
@ -67,5 +75,10 @@ export default mergeIds(taskId, task, {
|
||||
viewlet: {
|
||||
TableIssue: '' as Ref<Viewlet>,
|
||||
KanbanIssue: '' as Ref<Viewlet>
|
||||
},
|
||||
string: {
|
||||
ManageProjects: '' as IntlString,
|
||||
StateBacklog: '' as IntlString,
|
||||
StateActive: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -211,6 +211,7 @@ function defineFilters (builder: Builder): void {
|
||||
//
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: [
|
||||
'kind',
|
||||
'status',
|
||||
'priority',
|
||||
'space',
|
||||
@ -359,7 +360,8 @@ function defineApplication (
|
||||
['all', tracker.string.All, {}],
|
||||
['active', tracker.string.Active, {}],
|
||||
['backlog', tracker.string.Backlog, {}]
|
||||
]
|
||||
],
|
||||
allProjectsTypes: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -368,7 +370,6 @@ function defineApplication (
|
||||
icon: view.icon.Archive,
|
||||
label: tracker.string.AllProjects,
|
||||
position: 'bottom',
|
||||
visibleIf: workbench.function.IsOwner,
|
||||
spaceClass: tracker.class.Project,
|
||||
componentProps: {
|
||||
_class: tracker.class.Project,
|
||||
@ -686,22 +687,27 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
task.class.ProjectTypeCategory,
|
||||
task.class.ProjectTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
name: tracker.string.Projects,
|
||||
name: tracker.string.TrackerApplication,
|
||||
description: tracker.string.ManageWorkflowStatuses,
|
||||
icon: task.component.TemplatesIcon,
|
||||
attachedToClass: tracker.class.Project,
|
||||
statusClass: tracker.class.IssueStatus,
|
||||
statusCategories: [
|
||||
tracker.issueStatusCategory.Backlog,
|
||||
tracker.issueStatusCategory.Unstarted,
|
||||
tracker.issueStatusCategory.Started,
|
||||
tracker.issueStatusCategory.Completed,
|
||||
tracker.issueStatusCategory.Canceled
|
||||
]
|
||||
baseClass: tracker.class.Project
|
||||
},
|
||||
tracker.category.ProjectTypeCategory
|
||||
tracker.descriptors.ProjectType
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
task.class.TaskTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
baseClass: tracker.class.Issue,
|
||||
allowCreate: true,
|
||||
description: tracker.string.Issue,
|
||||
icon: tracker.icon.Issue,
|
||||
name: tracker.string.Issue
|
||||
},
|
||||
tracker.descriptors.Issue
|
||||
)
|
||||
}
|
||||
|
@ -13,31 +13,20 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, {
|
||||
DOMAIN_TX,
|
||||
type Data,
|
||||
type Ref,
|
||||
SortingOrder,
|
||||
type Status,
|
||||
type TxCollectionCUD,
|
||||
type TxCreateDoc,
|
||||
TxOperations,
|
||||
type TxUpdateDoc,
|
||||
toIdMap
|
||||
} from '@hcengineering/core'
|
||||
import core, { SortingOrder, TxOperations, generateId, type Data, type Ref, type Status } from '@hcengineering/core'
|
||||
import {
|
||||
createOrUpdate,
|
||||
tryMigrate,
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient,
|
||||
createOrUpdate,
|
||||
tryMigrate
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import { DOMAIN_TASK, createProjectType } from '@hcengineering/model-task'
|
||||
import { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
import { createProjectType, fixTaskTypes } from '@hcengineering/model-task'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { type Issue, TimeReportDayType, type TimeSpendReport } from '@hcengineering/tracker'
|
||||
import view from '@hcengineering/view'
|
||||
import task, { type TaskType } from '@hcengineering/task'
|
||||
import { TimeReportDayType } from '@hcengineering/tracker'
|
||||
import tracker from './plugin'
|
||||
import { DOMAIN_TRACKER } from './types'
|
||||
|
||||
async function createDefaultProject (tx: TxOperations): Promise<void> {
|
||||
const current = await tx.findOne(tracker.class.Project, {
|
||||
@ -48,14 +37,12 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
|
||||
objectId: tracker.project.DefaultProject
|
||||
})
|
||||
|
||||
// Create new if not deleted by customers.
|
||||
if (current === undefined && currentDeleted === undefined) {
|
||||
if ((await tx.findOne(task.class.ProjectType, { _id: tracker.ids.ClassingProjectType })) === undefined) {
|
||||
const categories = await tx.findAll(
|
||||
core.class.StatusCategory,
|
||||
{ ofAttribute: tracker.attribute.IssueStatus },
|
||||
{ sort: { order: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
const states: Omit<Data<Status>, 'rank'>[] = []
|
||||
|
||||
for (const category of categories) {
|
||||
@ -65,22 +52,85 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
|
||||
category: category._id
|
||||
})
|
||||
}
|
||||
await createProjectType(
|
||||
tx,
|
||||
{
|
||||
name: 'Classic project',
|
||||
descriptor: tracker.descriptors.ProjectType,
|
||||
description: '',
|
||||
tasks: []
|
||||
},
|
||||
[
|
||||
{
|
||||
_id: tracker.taskTypes.Issue,
|
||||
descriptor: tracker.descriptors.Issue,
|
||||
name: 'Issue',
|
||||
factory: states,
|
||||
ofClass: tracker.class.Issue,
|
||||
targetClass: tracker.class.Issue,
|
||||
statusCategories: categories.map((it) => it._id),
|
||||
statusClass: core.class.Status,
|
||||
kind: 'both',
|
||||
allowedAsChildOf: [tracker.taskTypes.Issue]
|
||||
}
|
||||
],
|
||||
tracker.ids.ClassingProjectType
|
||||
)
|
||||
}
|
||||
|
||||
const typeId = await createProjectType(
|
||||
if ((await tx.findOne(task.class.ProjectType, { _id: tracker.ids.BaseProjectType })) === undefined) {
|
||||
const issueId: Ref<TaskType> = generateId()
|
||||
const baseCategories = [
|
||||
task.statusCategory.UnStarted,
|
||||
task.statusCategory.Active,
|
||||
task.statusCategory.Won,
|
||||
task.statusCategory.Lost
|
||||
]
|
||||
const categories = await tx.findAll(
|
||||
core.class.StatusCategory,
|
||||
{ _id: { $in: baseCategories } },
|
||||
{ sort: { order: SortingOrder.Ascending } }
|
||||
)
|
||||
const states: Omit<Data<Status>, 'rank'>[] = []
|
||||
|
||||
for (const category of categories) {
|
||||
states.push({
|
||||
ofAttribute: tracker.attribute.IssueStatus,
|
||||
name: category.defaultStatusName,
|
||||
category: category._id
|
||||
})
|
||||
}
|
||||
await createProjectType(
|
||||
tx,
|
||||
{
|
||||
name: 'Base project',
|
||||
category: tracker.category.ProjectTypeCategory,
|
||||
description: ''
|
||||
descriptor: tracker.descriptors.ProjectType,
|
||||
description: '',
|
||||
tasks: []
|
||||
},
|
||||
states,
|
||||
tracker.ids.BaseProjectType,
|
||||
tracker.class.IssueStatus
|
||||
[
|
||||
{
|
||||
_id: issueId,
|
||||
name: 'Issue',
|
||||
descriptor: tracker.descriptors.Issue,
|
||||
factory: states,
|
||||
ofClass: tracker.class.Issue,
|
||||
targetClass: tracker.class.Issue,
|
||||
statusCategories: baseCategories,
|
||||
statusClass: core.class.Status,
|
||||
kind: 'both',
|
||||
allowedAsChildOf: [issueId]
|
||||
}
|
||||
],
|
||||
tracker.ids.BaseProjectType
|
||||
)
|
||||
}
|
||||
|
||||
// Create new if not deleted by customers.
|
||||
if (current === undefined && currentDeleted === undefined) {
|
||||
const state = await tx.findOne(
|
||||
tracker.class.IssueStatus,
|
||||
{ space: typeId },
|
||||
{ space: tracker.ids.DefaultProjectType },
|
||||
{ sort: { rank: SortingOrder.Ascending } }
|
||||
)
|
||||
if (state !== undefined) {
|
||||
@ -98,7 +148,7 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
|
||||
defaultIssueStatus: state._id,
|
||||
defaultTimeReportDay: TimeReportDayType.PreviousWorkDay,
|
||||
defaultAssignee: undefined,
|
||||
type: typeId
|
||||
type: tracker.ids.DefaultProjectType
|
||||
},
|
||||
tracker.project.DefaultProject
|
||||
)
|
||||
@ -123,184 +173,55 @@ async function createDefaults (tx: TxOperations): Promise<void> {
|
||||
)
|
||||
}
|
||||
|
||||
async function fixIconsWithEmojis (tx: TxOperations): Promise<void> {
|
||||
const projectsWithWrongIcon = await tx.findAll(tracker.class.Project, { icon: tracker.component.IconWithEmoji })
|
||||
const promises = []
|
||||
for (const project of projectsWithWrongIcon) {
|
||||
promises.push(tx.update(project, { icon: view.ids.IconWithEmoji }))
|
||||
}
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
async function fixSpentTime (client: MigrationClient): Promise<void> {
|
||||
const issues = await client.find<Issue>(DOMAIN_TASK, { reportedTime: { $gt: 0 } })
|
||||
for (const issue of issues) {
|
||||
const childInfo = issue.childInfo
|
||||
for (const child of childInfo ?? []) {
|
||||
child.reportedTime = child.reportedTime * 8
|
||||
}
|
||||
await client.update(DOMAIN_TASK, { _id: issue._id }, { reportedTime: issue.reportedTime * 8, childInfo })
|
||||
}
|
||||
const reports = await client.find<TimeSpendReport>(DOMAIN_TRACKER, {})
|
||||
for (const report of reports) {
|
||||
await client.update(DOMAIN_TRACKER, { _id: report._id }, { value: report.value * 8 })
|
||||
}
|
||||
const createTxes = await client.find<TxCollectionCUD<Issue, TimeSpendReport>>(DOMAIN_TX, {
|
||||
'tx.objectClass': tracker.class.TimeSpendReport,
|
||||
'tx._class': core.class.TxCreateDoc,
|
||||
'tx.attributes.value': { $exists: true }
|
||||
})
|
||||
for (const tx of createTxes) {
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _id: tx._id },
|
||||
{ 'tx.attributes.value': (tx.tx as TxCreateDoc<TimeSpendReport>).attributes.value * 8 }
|
||||
)
|
||||
}
|
||||
const updateTxes = await client.find<TxCollectionCUD<Issue, TimeSpendReport>>(DOMAIN_TX, {
|
||||
'tx.objectClass': tracker.class.TimeSpendReport,
|
||||
'tx._class': core.class.TxUpdateDoc,
|
||||
'tx.operations.value': { $exists: true }
|
||||
})
|
||||
for (const tx of updateTxes) {
|
||||
const val = (tx.tx as TxUpdateDoc<TimeSpendReport>).operations.value
|
||||
if (val !== undefined) {
|
||||
await client.update(DOMAIN_TX, { _id: tx._id }, { 'tx.operations.value': val * 8 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fixEstimation (client: MigrationClient): Promise<void> {
|
||||
const issues = await client.find<Issue>(DOMAIN_TASK, { estimation: { $gt: 0 } })
|
||||
for (const issue of issues) {
|
||||
const childInfo = issue.childInfo
|
||||
for (const child of childInfo ?? []) {
|
||||
child.estimation = child.estimation * 8
|
||||
}
|
||||
await client.update(DOMAIN_TASK, { _id: issue._id }, { estimation: issue.estimation * 8, childInfo })
|
||||
}
|
||||
const createTxes = await client.find<TxCollectionCUD<Issue, Issue>>(DOMAIN_TX, {
|
||||
'tx.objectClass': tracker.class.Issue,
|
||||
'tx._class': core.class.TxCreateDoc,
|
||||
'tx.attributes.estimation': { $gt: 0 }
|
||||
})
|
||||
for (const tx of createTxes) {
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _id: tx._id },
|
||||
{ 'tx.attributes.estimation': (tx.tx as TxCreateDoc<Issue>).attributes.estimation * 8 }
|
||||
)
|
||||
}
|
||||
const updateTxes = await client.find<TxCollectionCUD<Issue, Issue>>(DOMAIN_TX, {
|
||||
'tx.objectClass': tracker.class.Issue,
|
||||
'tx._class': core.class.TxUpdateDoc,
|
||||
'tx.operations.estimation': { $exists: true }
|
||||
})
|
||||
for (const tx of updateTxes) {
|
||||
const val = (tx.tx as TxUpdateDoc<Issue>).operations.estimation
|
||||
if (val !== undefined) {
|
||||
await client.update(DOMAIN_TX, { _id: tx._id }, { 'tx.operations.estimation': val * 8 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fixRemainingTime (client: MigrationClient): Promise<void> {
|
||||
while (true) {
|
||||
const issues = await client.find<Issue>(
|
||||
DOMAIN_TASK,
|
||||
{ _class: tracker.class.Issue, remainingTime: { $exists: false } },
|
||||
{ limit: 1000 }
|
||||
)
|
||||
for (const issue of issues) {
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _id: issue._id },
|
||||
{ remainingTime: Math.max(0, issue.estimation - issue.reportedTime) }
|
||||
)
|
||||
}
|
||||
if (issues.length === 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _class: { $ne: tracker.class.Issue }, remainingTime: { $exists: true } },
|
||||
{ $unset: { remainingTime: '' } }
|
||||
)
|
||||
}
|
||||
|
||||
async function fixParentsSpace (client: MigrationClient): Promise<void> {
|
||||
while (true) {
|
||||
const issues = await client.find<Issue>(
|
||||
DOMAIN_TASK,
|
||||
{ _class: tracker.class.Issue, 'parents.space': { $exists: false }, parents: { $exists: true, $ne: [] } },
|
||||
{ limit: 1000 }
|
||||
)
|
||||
|
||||
const parentIds = new Set<Ref<Issue>>()
|
||||
for (const i of issues) {
|
||||
for (const p of i.parents ?? []) {
|
||||
parentIds.add(p.parentId)
|
||||
async function fixTrackerTaskTypes (client: MigrationClient): Promise<void> {
|
||||
await fixTaskTypes(client, tracker.descriptors.ProjectType, async (t) => {
|
||||
const typeId: Ref<TaskType> = generateId()
|
||||
return [
|
||||
{
|
||||
_id: typeId,
|
||||
name: 'Issue',
|
||||
descriptor: tracker.descriptors.Issue,
|
||||
ofClass: tracker.class.Issue,
|
||||
targetClass: tracker.class.Issue,
|
||||
statusCategories: [
|
||||
tracker.issueStatusCategory.Backlog,
|
||||
tracker.issueStatusCategory.Unstarted,
|
||||
tracker.issueStatusCategory.Started,
|
||||
tracker.issueStatusCategory.Completed,
|
||||
tracker.issueStatusCategory.Canceled
|
||||
],
|
||||
statusClass: tracker.class.IssueStatus,
|
||||
kind: 'task',
|
||||
allowedAsChildOf: [typeId]
|
||||
}
|
||||
}
|
||||
|
||||
const parentIssues = toIdMap(
|
||||
await client.find<Issue>(DOMAIN_TASK, { _class: tracker.class.Issue, _id: { $in: Array.from(parentIds) } })
|
||||
)
|
||||
|
||||
for (const issue of issues) {
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _id: issue._id },
|
||||
{ parents: issue.parents.map((it) => ({ ...it, space: parentIssues.get(it.parentId)?.space ?? it.space })) }
|
||||
)
|
||||
}
|
||||
if (issues.length === 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _class: { $ne: tracker.class.Issue }, remainingTime: { $exists: true } },
|
||||
{ $unset: { remainingTime: '' } }
|
||||
)
|
||||
}
|
||||
|
||||
async function moveIssues (client: MigrationClient): Promise<void> {
|
||||
const docs = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue })
|
||||
if (docs.length > 0) {
|
||||
await client.move(DOMAIN_TRACKER, { _class: tracker.class.Issue }, DOMAIN_TASK)
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export const trackerOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, 'tracker', [
|
||||
{
|
||||
state: 'moveIssues',
|
||||
func: moveIssues
|
||||
state: 'fix-category-descriptors',
|
||||
func: async (client) => {
|
||||
await client.update(
|
||||
DOMAIN_SPACE,
|
||||
{ _class: task.class.ProjectType, category: 'tracker:category:ProjectTypeCategory' },
|
||||
{
|
||||
$set: { descriptor: tracker.descriptors.ProjectType },
|
||||
$unset: { category: 1 }
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'reportTimeDayToHour',
|
||||
func: fixSpentTime
|
||||
},
|
||||
{
|
||||
state: 'estimationDayToHour',
|
||||
func: fixEstimation
|
||||
},
|
||||
{
|
||||
state: 'fixRemainingTime',
|
||||
func: fixRemainingTime
|
||||
},
|
||||
{
|
||||
state: 'fixParentsSpace',
|
||||
func: fixParentsSpace
|
||||
state: 'fixTaskTypes',
|
||||
func: fixTrackerTaskTypes
|
||||
}
|
||||
])
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
await createDefaults(tx)
|
||||
await fixIconsWithEmojis(tx)
|
||||
}
|
||||
}
|
||||
|
@ -13,18 +13,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { type DocUpdateMessageViewlet, type TxViewlet } from '@hcengineering/activity'
|
||||
import { type ChatMessageViewlet } from '@hcengineering/chunter'
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
|
||||
import { type IntlString, type Resource, mergeIds } from '@hcengineering/platform'
|
||||
import { type ProjectType } from '@hcengineering/task'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import { type ProjectType, type TaskTypeDescriptor } from '@hcengineering/task'
|
||||
import { trackerId } from '@hcengineering/tracker'
|
||||
import tracker from '@hcengineering/tracker-resources/src/plugin'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { type Action, type ViewAction, type Viewlet } from '@hcengineering/view'
|
||||
import { type Application } from '@hcengineering/workbench'
|
||||
import { type DocUpdateMessageViewlet, type TxViewlet } from '@hcengineering/activity'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import { type ChatMessageViewlet } from '@hcengineering/chunter'
|
||||
|
||||
export default mergeIds(trackerId, tracker, {
|
||||
string: {
|
||||
@ -59,7 +59,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
MilestoneFilter: '' as AnyComponent,
|
||||
EditRelatedTargets: '' as AnyComponent,
|
||||
EditRelatedTargetsPopup: '' as AnyComponent,
|
||||
IssueSearchIcon: '' as AnyComponent
|
||||
IssueSearchIcon: '' as AnyComponent,
|
||||
MembersArrayEditor: '' as AnyComponent
|
||||
},
|
||||
app: {
|
||||
Tracker: '' as Ref<Application>
|
||||
@ -85,7 +86,9 @@ export default mergeIds(trackerId, tracker, {
|
||||
IssueChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
IssueTemplateChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
ComponentChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
MilestoneChatMessageViewlet: '' as Ref<ChatMessageViewlet>
|
||||
MilestoneChatMessageViewlet: '' as Ref<ChatMessageViewlet>,
|
||||
ClassingProjectType: '' as Ref<ProjectType>,
|
||||
DefaultProjectType: '' as Ref<ProjectType>
|
||||
},
|
||||
completion: {
|
||||
IssueQuery: '' as Resource<ObjectSearchFactory>,
|
||||
@ -106,5 +109,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
DeleteProject: '' as Ref<Action<Doc, Record<string, any>>>,
|
||||
DeleteProjectClean: '' as Ref<Action<Doc, Record<string, any>>>,
|
||||
DeleteIssue: '' as Ref<Action<Doc, Record<string, any>>>
|
||||
},
|
||||
descriptors: {
|
||||
Issue: '' as Ref<TaskTypeDescriptor>
|
||||
}
|
||||
})
|
||||
|
@ -69,7 +69,7 @@ import chunter from '@hcengineering/chunter'
|
||||
export const DOMAIN_TRACKER = 'tracker' as Domain
|
||||
|
||||
@Model(tracker.class.IssueStatus, core.class.Status)
|
||||
@UX(tracker.string.IssueStatuses, undefined, undefined, 'rank', 'name')
|
||||
@UX(tracker.string.IssueStatus, undefined, undefined, 'rank', 'name')
|
||||
export class TIssueStatus extends TStatus implements IssueStatus {}
|
||||
/**
|
||||
* @public
|
||||
@ -120,7 +120,7 @@ export class TProject extends TTaskProject implements Project {
|
||||
|
||||
declare defaultTimeReportDay: TimeReportDayType
|
||||
|
||||
@Prop(Collection(tracker.class.RelatedIssueTarget), tracker.string.RelatedIssue)
|
||||
@Prop(Collection(tracker.class.RelatedIssueTarget), tracker.string.RelatedIssues)
|
||||
relatedIssueTargets!: number
|
||||
}
|
||||
/**
|
||||
|
@ -26,6 +26,7 @@ import tags from '@hcengineering/tags'
|
||||
export const issuesOptions = (kanban: boolean): ViewOptionsModel => ({
|
||||
groupBy: [
|
||||
'status',
|
||||
'kind',
|
||||
'assignee',
|
||||
'priority',
|
||||
'component',
|
||||
@ -38,6 +39,7 @@ export const issuesOptions = (kanban: boolean): ViewOptionsModel => ({
|
||||
],
|
||||
orderBy: [
|
||||
['status', SortingOrder.Ascending],
|
||||
['kind', SortingOrder.Ascending],
|
||||
['priority', SortingOrder.Ascending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending],
|
||||
@ -95,6 +97,13 @@ export function issueConfig (
|
||||
props: { kind: 'list', size: 'small', justify: 'center' },
|
||||
displayProps: { key: key + 'status' }
|
||||
},
|
||||
// {
|
||||
// key: 'kind',
|
||||
// label: task.string.TaskType,
|
||||
// presenter: task.component.TaskTypePresenter,
|
||||
// props: { kind: 'list', size: 'small', justify: 'center' },
|
||||
// displayProps: { key: key + 'kind' }
|
||||
// },
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Title,
|
||||
@ -230,9 +239,10 @@ export function defineViewlets (builder: Builder): void {
|
||||
)
|
||||
|
||||
const subIssuesOptions: ViewOptionsModel = {
|
||||
groupBy: ['status', 'assignee', 'priority', 'milestone', 'createdBy', 'modifiedBy'],
|
||||
groupBy: ['status', 'kind', 'assignee', 'priority', 'milestone', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['rank', SortingOrder.Ascending],
|
||||
['kind', SortingOrder.Ascending],
|
||||
['status', SortingOrder.Ascending],
|
||||
['priority', SortingOrder.Ascending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
@ -508,8 +518,9 @@ export function defineViewlets (builder: Builder): void {
|
||||
attachTo: tracker.class.Project,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: {
|
||||
groupBy: ['createdBy'],
|
||||
groupBy: ['type', 'createdBy'],
|
||||
orderBy: [
|
||||
['type', SortingOrder.Descending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
@ -524,7 +535,26 @@ export function defineViewlets (builder: Builder): void {
|
||||
key: '',
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{ key: '', displayProps: { grow: true } }
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.MembersArrayEditor,
|
||||
sortingKey: 'members',
|
||||
props: { readonly: true, kind: 'list' }
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{
|
||||
key: 'defaultAssignee',
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
presenter: tracker.component.ModificationDatePresenter,
|
||||
displayProps: { fixed: 'right', dividerBefore: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
tracker.viewlet.ProjectList
|
||||
|
@ -427,7 +427,11 @@ export class Hierarchy {
|
||||
this.classifierProperties.delete(attribute.attributeOf)
|
||||
}
|
||||
|
||||
getAllAttributes (clazz: Ref<Classifier>, to?: Ref<Classifier>): Map<string, AnyAttribute> {
|
||||
getAllAttributes (
|
||||
clazz: Ref<Classifier>,
|
||||
to?: Ref<Classifier>,
|
||||
traverse?: (name: string, attr: AnyAttribute) => void
|
||||
): Map<string, AnyAttribute> {
|
||||
const result = new Map<string, AnyAttribute>()
|
||||
let ancestors = this.getAncestors(clazz)
|
||||
if (to !== undefined) {
|
||||
@ -448,6 +452,7 @@ export class Hierarchy {
|
||||
const attributes = this.attributes.get(cls)
|
||||
if (attributes !== undefined) {
|
||||
for (const [name, attr] of attributes) {
|
||||
traverse?.(name, attr)
|
||||
result.set(name, attr)
|
||||
}
|
||||
}
|
||||
|
@ -7,5 +7,5 @@
|
||||
"@typescript-eslint/array-type": "off",
|
||||
"@typescript-eslint/promise-function-async": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,14 @@
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "import"],
|
||||
"ignorePatterns": ["*.json", "node_modules/*", ".eslintrc.js"],
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".ts"],
|
||||
"moduleDirectory": ["src", "node_modules"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.svelte"],
|
||||
@ -30,13 +38,13 @@
|
||||
"@typescript-eslint/array-type": "off",
|
||||
"@typescript-eslint/promise-function-async": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "off",
|
||||
"import/first": "off",
|
||||
"import/no-duplicates": "off",
|
||||
"import/first": "warn",
|
||||
"import/no-duplicates": "warn",
|
||||
"import/no-mutable-exports": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-multiple-empty-lines": "off",
|
||||
"import/no-unresolved": "warn",
|
||||
"no-multiple-empty-lines": "warn",
|
||||
"no-undef-init": "off",
|
||||
"no-use-before-define": "off",
|
||||
"no-use-before-define": "warn",
|
||||
// This need to be enabled eventually
|
||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||
"@typescript-eslint/strict-boolean-expressions":"warn",
|
||||
|
@ -21,7 +21,7 @@ import type { IntlString, Plugin } from './platform'
|
||||
import { Severity, Status, unknownError } from './status'
|
||||
|
||||
import { getMetadata } from './metadata'
|
||||
import platform from './platform'
|
||||
import platform, { _EmbeddedId } from './platform'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -119,7 +119,7 @@ export async function translate<P extends Record<string, any>> (
|
||||
} else {
|
||||
try {
|
||||
const id = _parseId(message)
|
||||
if (id.component === 'embedded') {
|
||||
if (id.component === _EmbeddedId) {
|
||||
return id.name
|
||||
}
|
||||
const translation = (await getTranslation(id, locale)) ?? message
|
||||
|
@ -17,9 +17,9 @@
|
||||
// import core from '@hcengineering/core'
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import type { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import type { AnySvelteComponent, EditStyle } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { getAttribute, KeyedAttribute, updateAttribute } from '../attributes'
|
||||
import { KeyedAttribute, getAttribute, updateAttribute } from '../attributes'
|
||||
import { getAttributePresenterClass, getClient } from '../utils'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
@ -29,6 +29,7 @@
|
||||
export let focus: boolean = false
|
||||
export let editable = true
|
||||
export let focusIndex = -1
|
||||
export let editKind: EditStyle | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -42,7 +43,9 @@
|
||||
$: if (presenterClass !== undefined) {
|
||||
const typeClass = hierarchy.getClass(presenterClass.attrClass)
|
||||
const editorMixin = hierarchy.as(typeClass, view.mixin.AttributeEditor)
|
||||
editor = getResource(editorMixin.inlineEditor)
|
||||
if (editorMixin.inlineEditor !== undefined) {
|
||||
editor = getResource(editorMixin.inlineEditor)
|
||||
}
|
||||
}
|
||||
|
||||
function onChange (value: any) {
|
||||
@ -67,6 +70,7 @@
|
||||
{onChange}
|
||||
{focus}
|
||||
{focusIndex}
|
||||
{editKind}
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
|
@ -15,10 +15,17 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { Button, IconClose, Label, Scroller } from '@hcengineering/ui'
|
||||
import {
|
||||
Button,
|
||||
IconClose,
|
||||
Label,
|
||||
Scroller,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
resizeObserver,
|
||||
IconBack
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import presentation from '..'
|
||||
import { deviceOptionsStore as deviceInfo, resizeObserver, IconBack } from '@hcengineering/ui'
|
||||
import IconForward from './icons/Forward.svelte'
|
||||
|
||||
export let label: IntlString
|
||||
|
@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { AnyAttribute, DocIndexState, extractDocKey, isFullTextAttribute } from '@hcengineering/core'
|
||||
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { Icon } from '@hcengineering/ui'
|
||||
import { Label, Icon } from '@hcengineering/ui'
|
||||
import { getClient } from '../utils'
|
||||
|
||||
export let indexDoc: DocIndexState
|
||||
|
@ -17,10 +17,9 @@
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@hcengineering/ui'
|
||||
import { showPopup, Button } from '@hcengineering/ui'
|
||||
import { showPopup, Button, themeStore } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import presentation, { SpacesMultiPopup } from '..'
|
||||
import { themeStore } from '@hcengineering/ui'
|
||||
|
||||
export let selectedItems: Ref<Space>[] = []
|
||||
export let _classes: Ref<Class<Space>>[] = []
|
||||
|
@ -13,30 +13,33 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, ComponentType } from 'svelte'
|
||||
import { ComponentType, createEventDispatcher } from 'svelte'
|
||||
|
||||
import { Class, DocumentQuery, FindOptions, Ref, Space } from '@hcengineering/core'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { getPlatformColorDef, getPlatformColorForTextDef, IconWithEmoji, themeStore } from '@hcengineering/ui'
|
||||
import {
|
||||
AnySvelteComponent,
|
||||
Button,
|
||||
ButtonKind,
|
||||
ButtonSize,
|
||||
ButtonShape,
|
||||
ButtonSize,
|
||||
IconFolder,
|
||||
IconWithEmoji,
|
||||
Label,
|
||||
TooltipAlignment,
|
||||
eventToHTMLElement,
|
||||
getEventPositionElement,
|
||||
getFocusManager,
|
||||
IconFolder,
|
||||
Label,
|
||||
getPlatformColorDef,
|
||||
getPlatformColorForTextDef,
|
||||
showPopup,
|
||||
TooltipAlignment
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import view, { IconProps } from '@hcengineering/view'
|
||||
|
||||
import SpacesPopup from './SpacesPopup.svelte'
|
||||
import { ObjectCreate } from '../types'
|
||||
import { getClient } from '../utils'
|
||||
import SpacesPopup from './SpacesPopup.svelte'
|
||||
|
||||
export let _class: Ref<Class<Space>>
|
||||
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
|
||||
@ -69,20 +72,19 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const mgr = getFocusManager()
|
||||
async function updateSelected (value: Ref<Space> | undefined) {
|
||||
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
|
||||
async function updateSelected (_value: Ref<Space> | undefined, spaceQuery: DocumentQuery<Space> | undefined) {
|
||||
selected = _value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: _value }) : undefined
|
||||
|
||||
if (selected === undefined && autoSelect) {
|
||||
selected = (await findDefaultSpace?.()) ?? (await client.findOne(_class, { ...(spaceQuery ?? {}) }))
|
||||
if (selected !== undefined) {
|
||||
value = selected._id ?? undefined
|
||||
dispatch('change', value)
|
||||
dispatch('space', selected)
|
||||
}
|
||||
}
|
||||
dispatch('object', selected)
|
||||
}
|
||||
|
||||
$: updateSelected(value)
|
||||
$: updateSelected(value, spaceQuery)
|
||||
|
||||
const showSpacesPopup = (ev: MouseEvent) => {
|
||||
if (readonly) {
|
||||
@ -108,7 +110,6 @@
|
||||
(result) => {
|
||||
if (result !== undefined) {
|
||||
value = result?._id ?? undefined
|
||||
dispatch('change', value)
|
||||
mgr?.setFocusPos(focusIndex)
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,10 @@
|
||||
<script lang="ts">
|
||||
import { Class, DocumentQuery, FindOptions, Ref, Space } from '@hcengineering/core'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { AnySvelteComponent, ButtonKind, ButtonSize, ButtonShape } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, ButtonKind, ButtonShape, ButtonSize } from '@hcengineering/ui'
|
||||
import { ComponentType, createEventDispatcher } from 'svelte'
|
||||
import { ObjectCreate } from '../types'
|
||||
import SpaceSelect from './SpaceSelect.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { ComponentType } from 'svelte'
|
||||
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let _class: Ref<Class<Space>>
|
||||
@ -41,9 +40,13 @@
|
||||
export let readonly: boolean = false
|
||||
export let findDefaultSpace: (() => Promise<Space | undefined>) | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let create: ObjectCreate | undefined = undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
let _space = space
|
||||
$: if (_space !== space) {
|
||||
_space = space
|
||||
dispatch('change', space)
|
||||
}
|
||||
</script>
|
||||
|
||||
<SpaceSelect
|
||||
@ -67,10 +70,6 @@
|
||||
{iconWithEmoji}
|
||||
{defaultIcon}
|
||||
bind:value={space}
|
||||
on:change={(evt) => {
|
||||
space = evt.detail
|
||||
dispatch('change', space)
|
||||
}}
|
||||
on:space
|
||||
on:object
|
||||
{findDefaultSpace}
|
||||
/>
|
||||
|
@ -692,6 +692,7 @@ input.search {
|
||||
.min-w-60 { min-width: 15rem; }
|
||||
.min-w-80 { min-width: 20rem; }
|
||||
.min-w-100 { min-width: 25rem; }
|
||||
.min-w-144 { min-width: 25rem; }
|
||||
.min-w-168 { min-width: 42rem; }
|
||||
.min-w-min { min-width: min-content; }
|
||||
.min-h-0 { min-height: 0; }
|
||||
@ -841,6 +842,7 @@ a.no-line {
|
||||
.uppercase { text-transform: uppercase; }
|
||||
.lower { text-transform: lowercase; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right !important; }
|
||||
.text-center { text-align: center; }
|
||||
.leading-16px { line-height: 16px; }
|
||||
.leading-3 { line-height: .75rem; }
|
||||
|
@ -97,7 +97,9 @@
|
||||
}}
|
||||
>
|
||||
<span slot="content" class="overflow-label disabled" class:content-color={selectedItem === undefined}>
|
||||
{#if Array.isArray(selectedItem)}
|
||||
{#if $$slots.content}
|
||||
<slot name="content" />
|
||||
{:else if Array.isArray(selectedItem)}
|
||||
{#if selectedItem.length > 0}
|
||||
{#each selectedItem as seleceted, i}
|
||||
<span class="step-row">{seleceted.label}</span>
|
||||
|
@ -107,7 +107,7 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="scroll">
|
||||
<div class="scroll" class:mt-2={!enableSearch}>
|
||||
<div class="box">
|
||||
<ListView bind:this={list} count={objects.length} bind:selection>
|
||||
<svelte:fragment slot="item" let:item={idx}>
|
||||
|
@ -147,7 +147,7 @@
|
||||
class:flex-grow={fullSize}
|
||||
class:w-full={focusable || fullSize}
|
||||
class:uppercase
|
||||
on:click={() => {
|
||||
on:click|stopPropagation={() => {
|
||||
input.focus()
|
||||
}}
|
||||
use:resizeObserver={(element) => {
|
||||
@ -181,7 +181,9 @@
|
||||
on:change
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:blur
|
||||
on:blur={() => {
|
||||
dispatch('blur', value)
|
||||
}}
|
||||
/>
|
||||
{:else if format === 'number'}
|
||||
<input
|
||||
@ -196,7 +198,9 @@
|
||||
on:change
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:blur
|
||||
on:blur={() => {
|
||||
dispatch('blur', value)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
@ -210,7 +214,9 @@
|
||||
on:change
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:blur
|
||||
on:blur={() => {
|
||||
dispatch('blur', value)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
export let label: IntlString
|
||||
export let params: Record<string, any> = {}
|
||||
|
||||
let _value: string | undefined = undefined
|
||||
let _value: string | undefined
|
||||
|
||||
$: if (label !== undefined) {
|
||||
translate(label, params ?? {}, $themeStore.language)
|
||||
|
@ -21,6 +21,7 @@
|
||||
export let addClass: string | undefined = undefined
|
||||
export let noScroll: boolean = false
|
||||
export let kind: 'default' | 'thin' = 'default'
|
||||
export let updateOnMouse = true
|
||||
|
||||
const refs: HTMLElement[] = []
|
||||
|
||||
@ -75,10 +76,14 @@
|
||||
class="list-item{addClass ? ` ${addClass}` : ''}"
|
||||
class:selection={row === selection}
|
||||
on:mouseover={mouseAttractor(() => {
|
||||
onRow(row)
|
||||
if (updateOnMouse) {
|
||||
onRow(row)
|
||||
}
|
||||
})}
|
||||
on:mouseenter={mouseAttractor(() => {
|
||||
onRow(row)
|
||||
if (updateOnMouse) {
|
||||
onRow(row)
|
||||
}
|
||||
})}
|
||||
on:focus={() => {}}
|
||||
bind:this={refs[row]}
|
||||
@ -100,6 +105,9 @@
|
||||
margin: 0 0.5rem;
|
||||
min-width: 0;
|
||||
border-radius: 0.25rem;
|
||||
&:hover {
|
||||
background-color: var(--theme-popup-divider);
|
||||
}
|
||||
}
|
||||
&.thin {
|
||||
.list-item {
|
||||
|
@ -19,10 +19,10 @@
|
||||
import { closeTooltip, tooltipstore } from '../tooltips'
|
||||
import type { FadeOptions } from '../types'
|
||||
import { defaultSP } from '../types'
|
||||
import IconUpOutline from './icons/UpOutline.svelte'
|
||||
import { DelayedCaller } from '../utils'
|
||||
import IconDownOutline from './icons/DownOutline.svelte'
|
||||
import HalfUpDown from './icons/HalfUpDown.svelte'
|
||||
import { DelayedCaller } from '../utils'
|
||||
import IconUpOutline from './icons/UpOutline.svelte'
|
||||
|
||||
export let padding: string | undefined = undefined
|
||||
export let autoscroll: boolean = false
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { IntlString, Asset } from '@hcengineering/platform'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, ComponentType } from 'svelte'
|
||||
|
||||
import { DateRangeMode } from '@hcengineering/core'
|
||||
import ui from '../../plugin'
|
||||
@ -26,7 +26,6 @@
|
||||
import DPCalendar from './icons/DPCalendar.svelte'
|
||||
import DPCalendarOver from './icons/DPCalendarOver.svelte'
|
||||
import { getMonthName } from './internal/DateUtils'
|
||||
import { ComponentType } from 'svelte'
|
||||
|
||||
export let value: number | null | undefined
|
||||
export let mode: DateRangeMode = DateRangeMode.DATE
|
||||
|
39
packages/ui/src/components/icons/MoreV2.svelte
Normal file
39
packages/ui/src/components/icons/MoreV2.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M13 9.99988C13 10.5522 13.4477 10.9999 14 10.9999C14.5523 10.9999 15 10.5522 15 9.99988C15 9.44759 14.5523 8.99988 14 8.99988C13.4477 8.99988 13 9.44759 13 9.99988Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
<path
|
||||
d="M13 13.9999C13 14.5522 13.4477 14.9999 14 14.9999C14.5523 14.9999 15 14.5522 15 13.9999C15 13.4476 14.5523 12.9999 14 12.9999C13.4477 12.9999 13 13.4476 13 13.9999Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
<path
|
||||
d="M14 18.9999C13.4477 18.9999 13 18.5522 13 17.9999C13 17.4476 13.4477 16.9999 14 16.9999C14.5523 16.9999 15 17.4476 15 17.9999C15 18.5522 14.5523 18.9999 14 18.9999Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
<path
|
||||
d="M13 6C13 6.55228 13.4477 7 14 7C14.5523 7 15 6.55228 15 6C15 5.44772 14.5523 5 14 5C13.4477 5 13 5.44772 13 6Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
<path
|
||||
d="M9 9.99988C9 10.5522 9.44772 10.9999 10 10.9999C10.5523 10.9999 11 10.5522 11 9.99988C11 9.44759 10.5523 8.99988 10 8.99988C9.44772 8.99988 9 9.44759 9 9.99988Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
<path
|
||||
d="M9 13.9999C9 14.5522 9.44772 14.9999 10 14.9999C10.5523 14.9999 11 14.5522 11 13.9999C11 13.4476 10.5523 12.9999 10 12.9999C9.44772 12.9999 9 13.4476 9 13.9999Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
<path
|
||||
d="M10 18.9999C9.44772 18.9999 9 18.5522 9 17.9999C9 17.4476 9.44772 16.9999 10 16.9999C10.5523 16.9999 11 17.4476 11 17.9999C11 18.5522 10.5523 18.9999 10 18.9999Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
<path
|
||||
d="M9 6C9 6.55228 9.44772 7 10 7C10.5523 7 11 6.55228 11 6C11 5.44772 10.5523 5 10 5C9.44772 5 9 5.44772 9 6Z"
|
||||
fill="#8B97AD"
|
||||
/>
|
||||
</svg>
|
@ -15,9 +15,8 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { showPopup } from '../..'
|
||||
import ui, { showPopup, deviceOptionsStore as deviceInfo } from '../..'
|
||||
import LangPopup from './LangPopup.svelte'
|
||||
import ui, { deviceOptionsStore as deviceInfo } from '../..'
|
||||
|
||||
let pressed: boolean = false
|
||||
|
||||
|
@ -137,6 +137,7 @@ export { default as IconCalendar } from './components/icons/Calendar.svelte'
|
||||
export { default as IconFolder } from './components/icons/Folder.svelte'
|
||||
export { default as IconMoreH } from './components/icons/MoreH.svelte'
|
||||
export { default as IconMoreV } from './components/icons/MoreV.svelte'
|
||||
export { default as IconMoreV2 } from './components/icons/MoreV2.svelte'
|
||||
export { default as IconFile } from './components/icons/File.svelte'
|
||||
export { default as IconAttachment } from './components/icons/Attachment.svelte'
|
||||
export { default as IconThread } from './components/icons/Thread.svelte'
|
||||
|
@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import justClone from 'just-clone'
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
import { closePopup } from './popups'
|
||||
import justClone from 'just-clone'
|
||||
import { type Location as PlatformLocation } from './types'
|
||||
|
||||
export function locationToUrl (location: PlatformLocation): string {
|
||||
|
@ -13,13 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import contact, { Person } from '@hcengineering/contact'
|
||||
import { Class, Ref, Space } from '@hcengineering/core'
|
||||
import { SpaceMultiBoxList } from '@hcengineering/presentation'
|
||||
import { Component, DropdownLabelsIntl } from '@hcengineering/ui'
|
||||
import attachment from '../plugin'
|
||||
import { dateFileBrowserFilters, fileTypeFileBrowserFilters } from '..'
|
||||
import contact from '@hcengineering/contact'
|
||||
|
||||
export let requestedSpaceClasses: Ref<Class<Space>>[]
|
||||
export let spaceId: Ref<Space> | undefined
|
||||
|
@ -12,13 +12,23 @@
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ClassSetting } from '@hcengineering/setting-resources'
|
||||
import { Button, Expandable, Icon, IconAdd, IconDelete, IconEdit, Label, showPopup } from '@hcengineering/ui'
|
||||
import {
|
||||
Button,
|
||||
Expandable,
|
||||
Icon,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
IconEdit,
|
||||
Label,
|
||||
showPopup,
|
||||
CheckBox,
|
||||
DropdownLabelsPopup
|
||||
} from '@hcengineering/ui'
|
||||
import bitrix from '../plugin'
|
||||
|
||||
import AttributeMapper from './AttributeMapper.svelte'
|
||||
import FieldMappingPresenter from './FieldMappingPresenter.svelte'
|
||||
|
||||
import { CheckBox, DropdownLabelsPopup } from '@hcengineering/ui'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import BitrixFieldLookup from './BitrixFieldLookup.svelte'
|
||||
import CreateMappingAttribute from './CreateMappingAttribute.svelte'
|
||||
|
@ -13,9 +13,17 @@
|
||||
import core, { Class, Doc, generateId, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, getMetadata } from '@hcengineering/platform'
|
||||
import presentation, { getClient, SpaceSelect } from '@hcengineering/presentation'
|
||||
import { Button, CheckBox, Expandable, Icon, IconAdd, IconClose, Label } from '@hcengineering/ui'
|
||||
import { DropdownLabels } from '@hcengineering/ui'
|
||||
import { EditBox } from '@hcengineering/ui'
|
||||
import {
|
||||
Button,
|
||||
CheckBox,
|
||||
Expandable,
|
||||
Icon,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
Label,
|
||||
DropdownLabels,
|
||||
EditBox
|
||||
} from '@hcengineering/ui'
|
||||
import { NumberEditor } from '@hcengineering/view-resources'
|
||||
import bitrix from '../plugin'
|
||||
import FieldMappingPresenter from './FieldMappingPresenter.svelte'
|
||||
@ -136,9 +144,6 @@
|
||||
_class={core.class.Space}
|
||||
label={core.string.Space}
|
||||
bind:value={space}
|
||||
on:change={(evt) => {
|
||||
space = evt.detail
|
||||
}}
|
||||
autoSelect
|
||||
spaceQuery={{ _id: { $in: [contact.space.Contacts] } }}
|
||||
/>
|
||||
|
@ -8,8 +8,7 @@
|
||||
} from '@hcengineering/bitrix'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, DropdownTextItem, IconAdd, IconDelete } from '@hcengineering/ui'
|
||||
import { DropdownLabels } from '@hcengineering/ui'
|
||||
import { Button, DropdownTextItem, IconAdd, IconDelete, DropdownLabels } from '@hcengineering/ui'
|
||||
import bitrix from '../../plugin'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
|
@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Ref, Space } from '@hcengineering/core'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { Label, Button, Component, IconBack, IconClose } from '@hcengineering/ui'
|
||||
import board from '../plugin'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { MenuPage } from '@hcengineering/board'
|
||||
import { Button, Component, IconBack, IconClose } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
|
@ -16,13 +16,12 @@
|
||||
<script lang="ts">
|
||||
import { AttachmentDroppable, AttachmentsPresenter } from '@hcengineering/attachment-resources'
|
||||
import type { Card } from '@hcengineering/board'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import type { Ref, WithLookup } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import view from '@hcengineering/view'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import contact from '@hcengineering/contact'
|
||||
import {
|
||||
Button,
|
||||
Component,
|
||||
|
@ -1,17 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Icon } from '@hcengineering/ui'
|
||||
import { Icon, IconSize } from '@hcengineering/ui'
|
||||
import board from '@hcengineering/board'
|
||||
export let size: IconSize = 'small'
|
||||
</script>
|
||||
|
||||
<div class="flex-center template-icon">
|
||||
<Icon icon={board.icon.Board} size="small" />
|
||||
<Icon icon={board.icon.Board} {size} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.template-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
background-color: #4474f6;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import board from '../plugin'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { Label, Status as StatusControl, TextArea } from '@hcengineering/ui'
|
||||
import { Class, Client, Doc, Ref } from '@hcengineering/core'
|
||||
import { Class, Client, Doc, Ref, generateId, AttachedData } from '@hcengineering/core'
|
||||
import { getResource, OK, Resource, Status } from '@hcengineering/platform'
|
||||
import { Card as Popup, getClient } from '@hcengineering/presentation'
|
||||
import { Card } from '@hcengineering/board'
|
||||
@ -10,7 +10,6 @@
|
||||
import SpaceSelect from '../selectors/SpaceSelect.svelte'
|
||||
import StateSelect from '../selectors/StateSelect.svelte'
|
||||
import RankSelect from '../selectors/RankSelect.svelte'
|
||||
import { generateId, AttachedData } from '@hcengineering/core'
|
||||
import task from '@hcengineering/task'
|
||||
|
||||
export let value: Card
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Board, Card } from '@hcengineering/board'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Card } from '@hcengineering/board'
|
||||
import { Ref, Space } from '@hcengineering/core'
|
||||
import { IntlString, translate } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { DropdownLabels, DropdownTextItem, themeStore } from '@hcengineering/ui'
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
export let object: Card
|
||||
export let label: IntlString
|
||||
export let selected: Ref<Board>
|
||||
export let selected: Ref<Space>
|
||||
|
||||
let spaces: DropdownTextItem[] = []
|
||||
const spacesQuery = createQuery()
|
||||
|
@ -14,9 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
import { type TodoItem } from '@hcengineering/task'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import task, { type Project, type TodoItem } from '@hcengineering/task'
|
||||
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import Archive from './components/Archive.svelte'
|
||||
import BoardHeader from './components/BoardHeader.svelte'
|
||||
import BoardMenu from './components/BoardMenu.svelte'
|
||||
import BoardPresenter from './components/BoardPresenter.svelte'
|
||||
import CardPresenter from './components/CardPresenter.svelte'
|
||||
import CreateBoard from './components/CreateBoard.svelte'
|
||||
@ -25,29 +29,31 @@ import EditCard from './components/EditCard.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import KanbanView from './components/KanbanView.svelte'
|
||||
import LabelsView from './components/LabelsView.svelte'
|
||||
import MoveCard from './components/popups/MoveCard.svelte'
|
||||
import CopyCard from './components/popups/CopyCard.svelte'
|
||||
import DateRangePicker from './components/popups/DateRangePicker.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import BoardHeader from './components/BoardHeader.svelte'
|
||||
import BoardMenu from './components/BoardMenu.svelte'
|
||||
import MenuMainPage from './components/MenuMainPage.svelte'
|
||||
import Archive from './components/Archive.svelte'
|
||||
import TableView from './components/TableView.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import UserBoxList from './components/UserBoxList.svelte'
|
||||
import CardCoverEditor from './components/editor/CardCoverEditor.svelte'
|
||||
import CardCoverPresenter from './components/presenters/CardCoverPresenter.svelte'
|
||||
import CardCoverPicker from './components/popups/CardCoverPicker.svelte'
|
||||
import CopyCard from './components/popups/CopyCard.svelte'
|
||||
import DateRangePicker from './components/popups/DateRangePicker.svelte'
|
||||
import MoveCard from './components/popups/MoveCard.svelte'
|
||||
import CardCoverPresenter from './components/presenters/CardCoverPresenter.svelte'
|
||||
import { createCard, getCardFromTodoItem } from './utils/CardUtils'
|
||||
|
||||
async function ConvertToCard (object: TodoItem): Promise<void> {
|
||||
const client = getClient()
|
||||
const todoItemCard = await getCardFromTodoItem(client, object)
|
||||
if (todoItemCard === undefined) return
|
||||
|
||||
// TODO: Add filtering if requierd, or pass a type from UI
|
||||
const project = await client.findOne(task.class.Project, { _id: object.space as Ref<Project> })
|
||||
const taskTypes = await client.findAll(task.class.TaskType, { parent: project?.type })
|
||||
await createCard(client, todoItemCard.space, todoItemCard.status, {
|
||||
title: object.name,
|
||||
assignee: object.assignee,
|
||||
dueDate: object.dueTo
|
||||
dueDate: object.dueTo,
|
||||
kind: taskTypes[0]._id
|
||||
})
|
||||
|
||||
await client.remove(object)
|
||||
|
@ -18,7 +18,7 @@ export async function createCard (
|
||||
client: Client,
|
||||
space: Ref<Space>,
|
||||
status: Ref<Status>,
|
||||
attribues: Partial<AttachedData<Card>>
|
||||
attribues: Partial<AttachedData<Card>> & { kind: Card['kind'] }
|
||||
): Promise<Ref<Card>> {
|
||||
const sequence = await client.findOne(task.class.Sequence, { attachedTo: board.class.Card })
|
||||
if (sequence === undefined) {
|
||||
|
@ -20,7 +20,7 @@ import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { Preference } from '@hcengineering/preference'
|
||||
import { TagCategory } from '@hcengineering/tags'
|
||||
import type { Project, ProjectTypeCategory, Task } from '@hcengineering/task'
|
||||
import type { Project, ProjectTypeDescriptor, Task, TaskType } from '@hcengineering/task'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
import { Action, ActionCategory } from '@hcengineering/view'
|
||||
|
||||
@ -91,8 +91,13 @@ const boards = plugin(boardId, {
|
||||
},
|
||||
category: {
|
||||
Card: '' as Ref<ActionCategory>,
|
||||
Other: '' as Ref<TagCategory>,
|
||||
BoardType: '' as Ref<ProjectTypeCategory>
|
||||
Other: '' as Ref<TagCategory>
|
||||
},
|
||||
descriptors: {
|
||||
BoardType: '' as Ref<ProjectTypeDescriptor>
|
||||
},
|
||||
taskType: {
|
||||
Card: '' as Ref<TaskType>
|
||||
},
|
||||
attribute: {
|
||||
State: '' as Ref<Attribute<Status>>
|
||||
|
@ -15,9 +15,8 @@
|
||||
<script lang="ts">
|
||||
import { Message } from '@hcengineering/chunter'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { personByIdStore, Avatar } from '@hcengineering/contact-resources'
|
||||
import { Doc, IdMap, Ref } from '@hcengineering/core'
|
||||
import { Avatar } from '@hcengineering/contact-resources'
|
||||
import { Label, TimeSince } from '@hcengineering/ui'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { DocUpdates } from '@hcengineering/notification'
|
||||
|
@ -16,11 +16,10 @@
|
||||
import type { Comment } from '@hcengineering/chunter'
|
||||
import type { AttachedData, TxCreateDoc } from '@hcengineering/core'
|
||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import { AttachmentDocList } from '@hcengineering/attachment-resources'
|
||||
import { AttachmentDocList, AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import chunter from '../../plugin'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { LinkPresenter } from '@hcengineering/view-resources'
|
||||
|
||||
export let tx: TxCreateDoc<Comment>
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { ActivityMessage, ActivityMessageViewlet } from '@hcengineering/activity'
|
||||
import type { Person } from '@hcengineering/contact'
|
||||
import type {
|
||||
Account,
|
||||
@ -29,8 +30,7 @@ import { NotificationType } from '@hcengineering/notification'
|
||||
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { IntlString, plugin } from '@hcengineering/platform'
|
||||
import type { Preference } from '@hcengineering/preference'
|
||||
import { AnyComponent, ResolvedLocation } from '@hcengineering/ui'
|
||||
import { ActivityMessage, ActivityMessageViewlet } from '@hcengineering/activity'
|
||||
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
|
||||
import { Action } from '@hcengineering/view'
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
export let label: IntlString
|
||||
export let value: Ref<Account>[]
|
||||
export let onChange: (refs: Ref<Account>[]) => void
|
||||
export let onChange: ((refs: Ref<Account>[]) => void) | undefined
|
||||
export let readonly = false
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
@ -41,7 +41,7 @@
|
||||
}
|
||||
update = async () => {
|
||||
const accounts = await client.findAll(contact.class.PersonAccount, { person: { $in: evt.detail } })
|
||||
onChange(accounts.map((it) => it._id))
|
||||
onChange?.(accounts.map((it) => it._id))
|
||||
if (timer !== null) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
@ -52,7 +52,7 @@
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
update?.()
|
||||
void update?.()
|
||||
})
|
||||
|
||||
const excludedQuery = createQuery()
|
||||
|
@ -16,8 +16,9 @@
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { copyTextToClipboard } from '@hcengineering/presentation'
|
||||
import { PopupOptions, themeStore } from '@hcengineering/ui'
|
||||
import {
|
||||
PopupOptions,
|
||||
themeStore,
|
||||
Button,
|
||||
createFocusManager,
|
||||
FocusHandler,
|
||||
|
@ -16,10 +16,9 @@
|
||||
<script lang="ts">
|
||||
import type { AttachedData, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ButtonKind, ButtonSize, closeTooltip } from '@hcengineering/ui'
|
||||
import { ButtonKind, ButtonSize, closeTooltip, showPopup } from '@hcengineering/ui'
|
||||
|
||||
import { Channel, ChannelProvider } from '@hcengineering/contact'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import contact from '../plugin'
|
||||
import ChannelsDropdown from './ChannelsDropdown.svelte'
|
||||
|
||||
|
@ -69,7 +69,7 @@
|
||||
{allowDeselect}
|
||||
{titleDeselect}
|
||||
{placeholder}
|
||||
{docQuery}
|
||||
docQuery={readonly ? { ...docQuery, _id: { $in: selectedUsers } } : docQuery}
|
||||
{filter}
|
||||
groupBy={'_class'}
|
||||
bind:selectedObjects={selectedUsers}
|
||||
|
@ -194,7 +194,8 @@ export const contactPlugin = plugin(contactId, {
|
||||
UserBoxList: '' as AnyComponent,
|
||||
ChannelPresenter: '' as AnyComponent,
|
||||
SpaceMembers: '' as AnyComponent,
|
||||
DeleteConfirmationPopup: '' as AnyComponent
|
||||
DeleteConfirmationPopup: '' as AnyComponent,
|
||||
AccountArrayEditor: '' as AnyComponent
|
||||
},
|
||||
channelProvider: {
|
||||
Email: '' as Ref<ChannelProvider>,
|
||||
|
@ -13,8 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact'
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import { AccountRole, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { tzDateCompare, type Department, type Request, type RequestType, type Staff } from '@hcengineering/hr'
|
||||
import {
|
||||
|
@ -14,8 +14,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachedData, AttachedDoc, Doc, Ref } from '@hcengineering/core'
|
||||
import { generateId } from '@hcengineering/core'
|
||||
import { AttachedData, AttachedDoc, Doc, Ref, generateId } from '@hcengineering/core'
|
||||
import { OK, Status } from '@hcengineering/platform'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import type { Category } from '@hcengineering/inventory'
|
||||
|
@ -1,17 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Icon } from '@hcengineering/ui'
|
||||
import lead from '@hcengineering/lead'
|
||||
import { Icon, IconSize } from '@hcengineering/ui'
|
||||
export let size: IconSize = 'small'
|
||||
</script>
|
||||
|
||||
<div class="flex-center template-icon">
|
||||
<Icon icon={lead.icon.LeadApplication} size="small" />
|
||||
<Icon icon={lead.icon.LeadApplication} {size} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.template-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
background-color: #4474f6;
|
||||
}
|
||||
</style>
|
||||
|
@ -19,7 +19,7 @@ import type { Attribute, Class, Doc, Ref, Status, Timestamp } from '@hcengineeri
|
||||
import { Mixin } from '@hcengineering/core'
|
||||
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import type { Project, ProjectTypeCategory, Task } from '@hcengineering/task'
|
||||
import type { Project, ProjectTypeDescriptor, Task, TaskType } from '@hcengineering/task'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -82,8 +82,11 @@ const lead = plugin(leadId, {
|
||||
LeadApplication: '' as Asset,
|
||||
CreateCustomer: '' as Asset
|
||||
},
|
||||
category: {
|
||||
FunnelTypeCategory: '' as Ref<ProjectTypeCategory>
|
||||
descriptors: {
|
||||
FunnelType: '' as Ref<ProjectTypeDescriptor>
|
||||
},
|
||||
taskType: {
|
||||
Lead: '' as Ref<TaskType>
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -20,13 +20,12 @@
|
||||
StylishEdit,
|
||||
Label,
|
||||
Button,
|
||||
deviceOptionsStore as deviceInfo
|
||||
deviceOptionsStore as deviceInfo,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import StatusControl from './StatusControl.svelte'
|
||||
import { OK, Status, Severity } from '@hcengineering/platform'
|
||||
import { OK, Status, Severity, translate } from '@hcengineering/platform'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { themeStore } from '@hcengineering/ui'
|
||||
|
||||
import login from '../plugin'
|
||||
import { onMount } from 'svelte'
|
||||
|
@ -14,11 +14,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Popup, Scroller, deviceOptionsStore as deviceInfo, location, ticker } from '@hcengineering/ui'
|
||||
import { Popup, Scroller, deviceOptionsStore as deviceInfo, location, ticker, themeStore } from '@hcengineering/ui'
|
||||
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { themeStore } from '@hcengineering/ui'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
import { onDestroy } from 'svelte'
|
||||
import Confirmation from './Confirmation.svelte'
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import chunter, { getDirectChannel } from '@hcengineering/chunter'
|
||||
import { Employee, PersonAccount } from '@hcengineering/contact'
|
||||
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
|
||||
import { Class, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { DocUpdates } from '@hcengineering/notification'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
@ -32,7 +32,6 @@
|
||||
Separator
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { UsersPopup } from '@hcengineering/contact-resources'
|
||||
|
||||
import notification from '../plugin'
|
||||
|
@ -14,15 +14,16 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import type {
|
||||
NotificationGroup,
|
||||
NotificationPreferencesGroup,
|
||||
NotificationSetting,
|
||||
NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Label, Scroller, Separator, defineSeparators, settingsSeparators } from '@hcengineering/ui'
|
||||
import { Location, Scroller, getCurrentResolvedLocation, navigate, resolvedLocationStore } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import notification from '../plugin'
|
||||
import GroupElement from './GroupElement.svelte'
|
||||
import NotificationGroupSetting from './NotificationGroupSetting.svelte'
|
||||
@ -31,7 +32,7 @@
|
||||
let groups: NotificationGroup[] = []
|
||||
let preferencesGroups: NotificationPreferencesGroup[] = []
|
||||
|
||||
client.findAll(notification.class.NotificationGroup, {}).then((res) => {
|
||||
void client.findAll(notification.class.NotificationGroup, {}).then((res) => {
|
||||
groups = res
|
||||
})
|
||||
|
||||
@ -52,7 +53,7 @@
|
||||
let group: Ref<NotificationGroup> | undefined = undefined
|
||||
let currentPreferenceGroup: NotificationPreferencesGroup | undefined = undefined
|
||||
|
||||
client.findAll(notification.class.NotificationPreferencesGroup, {}).then((res) => {
|
||||
void client.findAll(notification.class.NotificationPreferencesGroup, {}).then((res) => {
|
||||
preferencesGroups = res
|
||||
})
|
||||
|
||||
@ -64,15 +65,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
defineSeparators('settingNotify', settingsSeparators)
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
void (async (loc: Location): Promise<void> => {
|
||||
group = loc.path[4] as Ref<NotificationGroup>
|
||||
currentPreferenceGroup = undefined
|
||||
})(loc)
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="flex h-full clear-mins">
|
||||
<div class="antiPanel-navigator">
|
||||
<div class="flex">
|
||||
<div class="antiPanel-element ml-4 mt-2">
|
||||
<div class="antiPanel-wrap__content">
|
||||
<div class="antiNav-header overflow-label">
|
||||
<Label label={notification.string.Notifications} />
|
||||
</div>
|
||||
<Scroller shrink>
|
||||
{#each preferencesGroups as preferenceGroup}
|
||||
<GroupElement
|
||||
@ -82,6 +87,9 @@
|
||||
on:click={() => {
|
||||
currentPreferenceGroup = preferenceGroup
|
||||
group = undefined
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path.length = 4
|
||||
navigate(loc)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
@ -96,6 +104,10 @@
|
||||
on:click={() => {
|
||||
group = gr._id
|
||||
currentPreferenceGroup = undefined
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[4] = group
|
||||
loc.path.length = 5
|
||||
navigate(loc)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
@ -103,8 +115,6 @@
|
||||
</Scroller>
|
||||
</div>
|
||||
</div>
|
||||
<Separator name={'settingNotify'} index={0} color={'var(--theme-navpanel-border)'} />
|
||||
|
||||
<div class="antiPanel-component filled">
|
||||
{#if group}
|
||||
<NotificationGroupSetting {group} {settings} />
|
||||
|
@ -15,13 +15,12 @@
|
||||
<script lang="ts">
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import contact, { Channel, getName, Person } from '@hcengineering/contact'
|
||||
import { ChannelsEditor } from '@hcengineering/contact-resources'
|
||||
import { ChannelsEditor, Avatar } from '@hcengineering/contact-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Avatar } from '@hcengineering/contact-resources'
|
||||
import { Component, Label } from '@hcengineering/ui'
|
||||
import { DocNavLink } from '@hcengineering/view-resources'
|
||||
import recruit from '../plugin'
|
||||
import notification from '@hcengineering/notification'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
|
||||
export let candidate: Person | undefined
|
||||
export let disabled: boolean = false
|
||||
@ -64,7 +63,7 @@
|
||||
<div class="footer">
|
||||
<div class="flex-row-center gap-2">
|
||||
<Component
|
||||
is={notification.component.ChatMessagesPresenter}
|
||||
is={chunter.component.ChatMessagesPresenter}
|
||||
props={{ value: candidate, size: 'small', showCounter: true }}
|
||||
/>
|
||||
<Component
|
||||
|
@ -41,7 +41,7 @@
|
||||
getClient
|
||||
} from '@hcengineering/presentation'
|
||||
import type { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
||||
import task, { calcRank, getStates } from '@hcengineering/task'
|
||||
import task, { TaskType, calcRank, getStates } from '@hcengineering/task'
|
||||
import ui, {
|
||||
Button,
|
||||
ColorPopup,
|
||||
@ -62,7 +62,7 @@
|
||||
import CandidateCard from './CandidateCard.svelte'
|
||||
import VacancyCard from './VacancyCard.svelte'
|
||||
import VacancyOrgPresenter from './VacancyOrgPresenter.svelte'
|
||||
import { typeStore } from '@hcengineering/task-resources'
|
||||
import { TaskKindSelector, selectedTypeStore, typeStore } from '@hcengineering/task-resources'
|
||||
|
||||
export let space: Ref<Vacancy>
|
||||
export let candidate: Ref<Candidate>
|
||||
@ -94,7 +94,9 @@
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: '' as Ref<Account>,
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
dueDate: null,
|
||||
kind: '' as Ref<TaskType>,
|
||||
isDone: false
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -106,7 +108,7 @@
|
||||
return (preserveCandidate || _candidate === undefined) && assignee === undefined
|
||||
}
|
||||
|
||||
async function createApplication () {
|
||||
async function createApplication (): Promise<void> {
|
||||
if (selectedState === undefined) {
|
||||
throw new Error(`Please select initial state:${_space}`)
|
||||
}
|
||||
@ -114,6 +116,9 @@
|
||||
if (sequence === undefined) {
|
||||
throw new Error('sequence object not found')
|
||||
}
|
||||
if (kind === undefined) {
|
||||
throw new Error('kind is not specified')
|
||||
}
|
||||
|
||||
const lastOne = await client.findOne(recruit.class.Applicant, {}, { sort: { rank: SortingOrder.Descending } })
|
||||
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
|
||||
@ -145,7 +150,8 @@
|
||||
assignee: doc.assignee,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
dueDate: null,
|
||||
kind
|
||||
},
|
||||
doc._id
|
||||
)
|
||||
@ -209,6 +215,8 @@
|
||||
return { id: s._id, label: s.name, color: s.color ?? getColorNumberByText(s.name) }
|
||||
})
|
||||
|
||||
let kind: Ref<TaskType> | undefined
|
||||
|
||||
const manager = createFocusManager()
|
||||
|
||||
const existingApplicationsQuery = createQuery()
|
||||
@ -268,6 +276,7 @@
|
||||
<svelte:fragment slot="title">
|
||||
<div class="flex-row-center gap-2">
|
||||
<Label label={recruit.string.CreateApplication} />
|
||||
<TaskKindSelector projectType={vacancy?.type} bind:taskType={kind} baseClass={recruit.class.Applicant} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<StatusControl slot="error" {status} />
|
||||
@ -300,7 +309,7 @@
|
||||
<div class="flex-grow">
|
||||
<SpaceSelect
|
||||
_class={recruit.class.Vacancy}
|
||||
spaceQuery={{ archived: false }}
|
||||
spaceQuery={{ archived: false, ...($selectedTypeStore !== undefined ? { type: $selectedTypeStore } : {}) }}
|
||||
spaceOptions={orgOptions}
|
||||
readonly={preserveVacancy}
|
||||
label={recruit.string.Vacancy}
|
||||
@ -309,9 +318,6 @@
|
||||
label: recruit.string.CreateVacancy
|
||||
}}
|
||||
bind:value={_space}
|
||||
on:change={(evt) => {
|
||||
_space = evt.detail
|
||||
}}
|
||||
component={VacancyOrgPresenter}
|
||||
componentProps={{ inline: true }}
|
||||
>
|
||||
|
@ -35,12 +35,16 @@
|
||||
import recruit from '../plugin'
|
||||
import Company from './icons/Company.svelte'
|
||||
import Vacancy from './icons/Vacancy.svelte'
|
||||
import { selectedTypeStore, typeStore } from '@hcengineering/task-resources'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let name: string = ''
|
||||
let template: ProjectType | undefined
|
||||
let typeId: Ref<ProjectType> | undefined
|
||||
let typeId: Ref<ProjectType> | undefined = $selectedTypeStore
|
||||
|
||||
$: typeType = typeId !== undefined ? $typeStore.get(typeId) : undefined
|
||||
|
||||
let appliedTemplateId: Ref<ProjectType> | undefined
|
||||
let objectId: Ref<VacancyClass> = generateId()
|
||||
let issueTemplates: FindResult<IssueTemplate>
|
||||
@ -144,8 +148,8 @@
|
||||
return resId
|
||||
}
|
||||
|
||||
async function createVacancy () {
|
||||
if (typeId === undefined) {
|
||||
async function createVacancy (): Promise<void> {
|
||||
if (typeId === undefined || typeType === undefined) {
|
||||
throw Error(`Failed to find target project type: ${typeId}`)
|
||||
}
|
||||
|
||||
@ -184,6 +188,10 @@
|
||||
}
|
||||
|
||||
await descriptionBox.createAttachments()
|
||||
|
||||
// Add vacancy mixin
|
||||
await client.createMixin(objectId, recruit.class.Vacancy, core.space.Space, typeType.targetClass, {})
|
||||
|
||||
objectId = generateId()
|
||||
|
||||
dispatch('close', id)
|
||||
@ -226,6 +234,7 @@
|
||||
}}
|
||||
on:changeContent
|
||||
>
|
||||
{typeType?.name}
|
||||
<div class="flex-row-center clear-mins">
|
||||
<div class="mr-3">
|
||||
<Button focusIndex={1} icon={Vacancy} size={'medium'} kind={'link-bordered'} noFocus />
|
||||
@ -275,7 +284,7 @@
|
||||
<Component
|
||||
is={task.component.ProjectTypeSelector}
|
||||
props={{
|
||||
categories: [recruit.category.VacancyTypeCategories],
|
||||
descriptors: [recruit.descriptors.VacancyType],
|
||||
type: typeId,
|
||||
focusIndex: 4,
|
||||
kind: 'regular',
|
||||
|
@ -26,7 +26,7 @@
|
||||
import { Component, DueDatePresenter } from '@hcengineering/ui'
|
||||
import { BuildModelKey } from '@hcengineering/view'
|
||||
import { DocNavLink, ObjectPresenter, enabledConfig, statusStore } from '@hcengineering/view-resources'
|
||||
import { ChatMessagesPresenter } from '@hcengineering/notification-resources'
|
||||
import { ChatMessagesPresenter } from '@hcengineering/chunter-resources'
|
||||
|
||||
import ApplicationPresenter from './ApplicationPresenter.svelte'
|
||||
|
||||
@ -135,13 +135,15 @@
|
||||
{/if}
|
||||
{#if enabledConfig(config, 'comments')}
|
||||
<ChatMessagesPresenter value={object.comments} {object} kind="list" size="x-small" />
|
||||
<ChatMessagesPresenter
|
||||
value={object.$lookup?.attachedTo?.comments}
|
||||
object={object.$lookup?.attachedTo}
|
||||
withInput={false}
|
||||
kind="list"
|
||||
size="x-small"
|
||||
/>
|
||||
{#if object.$lookup?.attachedTo}
|
||||
<ChatMessagesPresenter
|
||||
value={object.$lookup?.attachedTo?.comments}
|
||||
object={object.$lookup?.attachedTo}
|
||||
withInput={false}
|
||||
kind="list"
|
||||
size="x-small"
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{#if enabledConfig(config, 'assignee')}
|
||||
|
@ -172,9 +172,6 @@
|
||||
label: recruit.string.CreateVacancy
|
||||
}}
|
||||
bind:value={_space}
|
||||
on:change={(evt) => {
|
||||
_space = evt.detail
|
||||
}}
|
||||
component={VacancyOrgPresenter}
|
||||
componentProps={{ inline: true }}
|
||||
>
|
||||
|
@ -155,9 +155,6 @@
|
||||
label: recruit.string.CreateVacancy
|
||||
}}
|
||||
bind:value={_space}
|
||||
on:change={(evt) => {
|
||||
_space = evt.detail
|
||||
}}
|
||||
component={VacancyOrgPresenter}
|
||||
componentProps={{ inline: true }}
|
||||
>
|
||||
|
@ -17,8 +17,7 @@
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import tags, { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { Button, CheckBox, EditBox, Lazy, ListView, Loading } from '@hcengineering/ui'
|
||||
import { Expandable } from '@hcengineering/ui'
|
||||
import { Button, CheckBox, EditBox, Lazy, ListView, Loading, Expandable } from '@hcengineering/ui'
|
||||
import { FILTER_DEBOUNCE_MS } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
|
@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { IconSize } from '@hcengineering/ui'
|
||||
import VacancyIcon from './icons/Vacancy.svelte'
|
||||
export let size: IconSize = 'small'
|
||||
</script>
|
||||
|
||||
<div class="flex-center template-icon">
|
||||
<VacancyIcon size="small" />
|
||||
<VacancyIcon {size} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.template-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
background-color: #60b96e;
|
||||
}
|
||||
</style>
|
||||
|
@ -23,7 +23,7 @@
|
||||
import { NavLink } from '@hcengineering/view-resources'
|
||||
import recruit from '../plugin'
|
||||
import VacancyIcon from './icons/Vacancy.svelte'
|
||||
import notification from '@hcengineering/notification'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
|
||||
export let vacancy: WithLookup<Vacancy> | undefined
|
||||
export let disabled: boolean = false
|
||||
@ -97,7 +97,7 @@
|
||||
<div class="footer">
|
||||
<div class="flex-row-center gap-2">
|
||||
<Component
|
||||
is={notification.component.ChatMessagesPresenter}
|
||||
is={chunter.component.ChatMessagesPresenter}
|
||||
props={{ value: vacancy, size: 'small', showCounter: true }}
|
||||
/>
|
||||
<Component
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { ProjectType } from '@hcengineering/task'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import tracker from '@hcengineering/tracker'
|
||||
import { Button, Component, EditBox, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import { Button, Component, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import { getFiltredKeys } from '@hcengineering/view-resources'
|
||||
import recruit from '../plugin'
|
||||
|
||||
@ -29,25 +29,11 @@
|
||||
const customKeys = getFiltredKeys(hierarchy, type._class, []).filter((key) => key.attr.isCustom)
|
||||
|
||||
async function onDescriptionChange (value: string) {
|
||||
await client.update(type, { description: value })
|
||||
}
|
||||
|
||||
async function onShortDescriptionChange (value: string) {
|
||||
await client.update(type, { shortDescription: value })
|
||||
await client.diffUpdate(type, { description: 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={type.shortDescription}
|
||||
on:change={() => onShortDescriptionChange(type.shortDescription ?? '')}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-9">
|
||||
<div class="mt-4">
|
||||
<div class="flex-no-shrink flex-between trans-title uppercase">
|
||||
<Label label={recruit.string.FullDescription} />
|
||||
</div>
|
||||
@ -56,6 +42,7 @@
|
||||
<StyledTextBox
|
||||
kind={'emphasized'}
|
||||
alwaysEdit
|
||||
maxHeight={'card'}
|
||||
showButtons={false}
|
||||
content={type.description ?? ''}
|
||||
on:value={(evt) => onDescriptionChange(evt.detail)}
|
||||
|
@ -14,17 +14,17 @@
|
||||
//
|
||||
|
||||
import {
|
||||
toIdMap,
|
||||
type Client,
|
||||
type Doc,
|
||||
type DocumentQuery,
|
||||
type FindResult,
|
||||
type ObjQueryType,
|
||||
type Ref,
|
||||
type RelatedDocument,
|
||||
toIdMap
|
||||
type RelatedDocument
|
||||
} from '@hcengineering/core'
|
||||
import { OK, type Resources, Severity, Status } from '@hcengineering/platform'
|
||||
import { type ObjectSearchResult, createQuery } from '@hcengineering/presentation'
|
||||
import { OK, Severity, Status, type Resources } from '@hcengineering/platform'
|
||||
import { createQuery, type ObjectSearchResult } from '@hcengineering/presentation'
|
||||
import { type Applicant, type Candidate, type Vacancy } from '@hcengineering/recruit'
|
||||
import task from '@hcengineering/task'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
@ -49,13 +49,13 @@ import SkillsView from './components/SkillsView.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import Vacancies from './components/Vacancies.svelte'
|
||||
import VacancyCountPresenter from './components/VacancyCountPresenter.svelte'
|
||||
import VacancyEditor from './components/VacancyEditor.svelte'
|
||||
import VacancyItem from './components/VacancyItem.svelte'
|
||||
import VacancyItemPresenter from './components/VacancyItemPresenter.svelte'
|
||||
import VacancyList from './components/VacancyList.svelte'
|
||||
import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte'
|
||||
import VacancyPresenter from './components/VacancyPresenter.svelte'
|
||||
import VacancyTemplateEditor from './components/VacancyTemplateEditor.svelte'
|
||||
import VacancyEditor from './components/VacancyEditor.svelte'
|
||||
import CreateOpinion from './components/review/CreateOpinion.svelte'
|
||||
import CreateReview from './components/review/CreateReview.svelte'
|
||||
import EditReview from './components/review/EditReview.svelte'
|
||||
@ -78,8 +78,8 @@ import {
|
||||
resolveLocation
|
||||
} from './utils'
|
||||
|
||||
import { MoveApplicant } from './actionImpl'
|
||||
import { get } from 'svelte/store'
|
||||
import { MoveApplicant } from './actionImpl'
|
||||
|
||||
async function createOpinion (object: Doc): Promise<void> {
|
||||
showPopup(CreateOpinion, { space: object.space, review: object._id })
|
||||
|
@ -30,7 +30,7 @@ import type {
|
||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { TagReference } from '@hcengineering/tags'
|
||||
import type { Project, ProjectTypeCategory, Task } from '@hcengineering/task'
|
||||
import type { Project, ProjectTypeDescriptor, Task, TaskType } from '@hcengineering/task'
|
||||
import { AnyComponent, ResolvedLocation } from '@hcengineering/ui'
|
||||
|
||||
/**
|
||||
@ -162,8 +162,8 @@ const recruit = plugin(recruitId, {
|
||||
Review: '' as Ref<Class<Review>>,
|
||||
Opinion: '' as Ref<Class<Opinion>>
|
||||
},
|
||||
category: {
|
||||
VacancyTypeCategories: '' as Ref<ProjectTypeCategory>
|
||||
descriptors: {
|
||||
VacancyType: '' as Ref<ProjectTypeDescriptor>
|
||||
},
|
||||
mixin: {
|
||||
Candidate: '' as Ref<Mixin<Candidate>>,
|
||||
@ -200,6 +200,9 @@ const recruit = plugin(recruitId, {
|
||||
},
|
||||
space: {
|
||||
Reviews: '' as Ref<Calendar>
|
||||
},
|
||||
taskTypes: {
|
||||
Applicant: '' as Ref<TaskType>
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"string": {
|
||||
"Setting": "Setting",
|
||||
"ManageProjects": "Manage Projects",
|
||||
"Setting": "Setting",
|
||||
"Integrations": "Integrations",
|
||||
"Support": "Support",
|
||||
"Privacy": "Privacy",
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"string": {
|
||||
"Setting": "Настройки",
|
||||
"ManageProjects": "Управление проектами",
|
||||
"Integrations": "Интеграции",
|
||||
"Support": "Поддержка",
|
||||
"Privacy": "Конфиденциальность",
|
||||
|
@ -28,34 +28,29 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="antiNav-element"
|
||||
class="antiNav-element flex-row-center flex-between"
|
||||
class:selected
|
||||
class:expandable
|
||||
on:click|stopPropagation={() => {
|
||||
dispatch('click')
|
||||
}}
|
||||
>
|
||||
<div class="an-element__icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
<div class="flex-row-center flex flex-between flex-grow">
|
||||
<div class="flex-row-center">
|
||||
<div class="an-element__icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="an-element__label" class:trans-title={expandable}>
|
||||
{#if label}<Label {label} />{/if}
|
||||
</span>
|
||||
</div>
|
||||
<slot name="tools" />
|
||||
</div>
|
||||
<span class="an-element__label">
|
||||
{#if label}<Label {label} />{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.expandable {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '▶';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.5rem;
|
||||
font-size: 0.375rem;
|
||||
color: var(--dark-color);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -26,33 +26,34 @@
|
||||
RefTo,
|
||||
Type
|
||||
} from '@hcengineering/core'
|
||||
import { getResource, IntlString } from '@hcengineering/platform'
|
||||
import presentation, { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { IntlString, getResource } from '@hcengineering/platform'
|
||||
import presentation, { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
Action,
|
||||
ActionIcon,
|
||||
AnySvelteComponent,
|
||||
CircleButton,
|
||||
Component,
|
||||
getEventPositionElement,
|
||||
Icon,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
IconEdit,
|
||||
IconMoreV,
|
||||
IconMoreV2,
|
||||
Label,
|
||||
Menu,
|
||||
getEventPositionElement,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { getContextActions } from '@hcengineering/view-resources/src/actions'
|
||||
import { getContextActions } from '@hcengineering/view-resources'
|
||||
import settings from '../plugin'
|
||||
import CreateAttribute from './CreateAttribute.svelte'
|
||||
import EditAttribute from './EditAttribute.svelte'
|
||||
import EditClassLabel from './EditClassLabel.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let ofClass: Ref<Class<Doc>> | undefined
|
||||
export let ofClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let useOfClassAttributes = true
|
||||
export let showTitle = true
|
||||
export let showCreate = true
|
||||
|
||||
export let attributeMapper:
|
||||
| {
|
||||
@ -60,7 +61,7 @@
|
||||
label: IntlString
|
||||
props: Record<string, any>
|
||||
}
|
||||
| undefined
|
||||
| undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -77,9 +78,10 @@
|
||||
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
|
||||
const cl = hierarchy.getClass(_class)
|
||||
const attributes = Array.from(
|
||||
hierarchy.getAllAttributes(_class, _class === ofClass ? core.class.Doc : cl.extends).values()
|
||||
hierarchy
|
||||
.getAllAttributes(_class, _class === ofClass && useOfClassAttributes ? core.class.Doc : cl.extends)
|
||||
.values()
|
||||
)
|
||||
// const filtred = attributes.filter((p) => !p.hidden)
|
||||
return attributes
|
||||
}
|
||||
|
||||
@ -89,19 +91,19 @@
|
||||
attributes = getCustomAttributes(_class)
|
||||
})
|
||||
|
||||
function update () {
|
||||
function update (): void {
|
||||
attributes = getCustomAttributes(_class)
|
||||
}
|
||||
|
||||
function createAttribute () {
|
||||
export function createAttribute (): void {
|
||||
showPopup(CreateAttribute, { _class }, 'top', update)
|
||||
}
|
||||
|
||||
async function editAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
export async function editAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
showPopup(EditAttribute, { attribute, exist }, 'top', update)
|
||||
}
|
||||
|
||||
async function removeAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
export async function removeAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
@ -110,7 +112,7 @@
|
||||
},
|
||||
'top',
|
||||
async (result) => {
|
||||
if (result) {
|
||||
if (result != null) {
|
||||
await client.remove(attribute)
|
||||
update()
|
||||
}
|
||||
@ -118,7 +120,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function showMenu (ev: MouseEvent, attribute: AnyAttribute) {
|
||||
async function showMenu (ev: MouseEvent, attribute: AnyAttribute): Promise<void> {
|
||||
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
|
||||
|
||||
const actions: Action[] = [
|
||||
@ -126,16 +128,16 @@
|
||||
label: presentation.string.Edit,
|
||||
icon: IconEdit,
|
||||
action: async () => {
|
||||
editAttribute(attribute, exist)
|
||||
await editAttribute(attribute, exist)
|
||||
}
|
||||
}
|
||||
]
|
||||
if (attribute.isCustom) {
|
||||
if (attribute.isCustom === true) {
|
||||
actions.push({
|
||||
label: presentation.string.Remove,
|
||||
icon: IconDelete,
|
||||
action: async () => {
|
||||
removeAttribute(attribute, exist)
|
||||
await removeAttribute(attribute, exist)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -146,7 +148,7 @@
|
||||
icon: it.icon,
|
||||
action: async (_: any, evt: Event) => {
|
||||
const r = await getResource(it.action)
|
||||
r(attribute, evt, it.actionProps)
|
||||
await r(attribute, evt, it.actionProps)
|
||||
}
|
||||
}))
|
||||
)
|
||||
@ -176,106 +178,105 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center fs-title mb-3">
|
||||
{#if clazz?.icon}
|
||||
<div class="mr-2 flex">
|
||||
<Icon icon={clazz.icon} size={'medium'} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<Icon icon={IconAdd} size={'x-small'} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if clazz}
|
||||
<Label label={clazz.label} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<div class="ml-2">
|
||||
<ActionIcon icon={IconEdit} size="small" action={editLabel} />
|
||||
{#if showTitle}
|
||||
<div class="flex-row-center fs-title mb-3">
|
||||
{#if clazz?.icon}
|
||||
<div class="mr-2 flex">
|
||||
<Icon icon={clazz.icon} size={'medium'} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<Icon icon={IconAdd} size={'x-small'} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-between trans-title mb-3">
|
||||
<Label label={settings.string.Attributes} />
|
||||
<CircleButton icon={IconAdd} size="medium" on:click={createAttribute} />
|
||||
</div>
|
||||
<table class="antiTable">
|
||||
<thead class="scroller-thead">
|
||||
<tr class="scroller-thead__tr">
|
||||
<!-- <th>Field name</th> -->
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Attribute} />
|
||||
{#if clazz}
|
||||
<Label label={clazz.label} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<div class="ml-2">
|
||||
<ActionIcon icon={IconEdit} size="small" action={editLabel} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Type} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Visibility} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
</th>
|
||||
{#if attributeMapper}
|
||||
<th>
|
||||
<Label label={attributeMapper.label} />
|
||||
</th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each attributes as attr}
|
||||
{@const attrType = getAttrType(attr.type)}
|
||||
<tr
|
||||
class="antiTable-body__row"
|
||||
on:contextmenu={(ev) => {
|
||||
ev.preventDefault()
|
||||
showMenu(ev, attr)
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<div class="antiTable-cells__firstCell whitespace-nowrap">
|
||||
<Label label={attr.label} />
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div id="context-menu" class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, attr)}>
|
||||
<IconMoreV size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showCreate}
|
||||
<div class="flex-between trans-title mb-3">
|
||||
<Label label={settings.string.Attributes} />
|
||||
<CircleButton icon={IconAdd} size="medium" on:click={createAttribute} />
|
||||
</div>
|
||||
{/if}
|
||||
{#each attributes as attr, i}
|
||||
{@const attrType = getAttrType(attr.type)}
|
||||
<tr
|
||||
class="antiTable-body__row"
|
||||
on:contextmenu={(ev) => {
|
||||
ev.preventDefault()
|
||||
void showMenu(ev, attr)
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
{#if i === 0 && clazz?.label !== undefined}
|
||||
<div class="trans-title">
|
||||
<Label label={clazz.label} />
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<div class="antiTable-cells__firstCell whitespace-nowrap flex-row-center">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div id="context-menu" on:click={(ev) => showMenu(ev, attr)}>
|
||||
<div class="p-1">
|
||||
<IconMoreV2 size={'medium'} />
|
||||
</div>
|
||||
</div>
|
||||
{#if attr.icon !== undefined}
|
||||
<div class="p-1">
|
||||
<Icon icon={attr.icon} size={'small'} />
|
||||
</div>
|
||||
</td>
|
||||
<td class="select-text whitespace-nowrap">
|
||||
<Label label={attr.type.label} />
|
||||
{#if attrType !== undefined}
|
||||
: <Label label={attrType} />
|
||||
{/if}
|
||||
{#if attr.type._class === core.class.EnumOf}
|
||||
{#await getEnumName(attr.type) then name}
|
||||
{#if name}
|
||||
: {name}
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
{#if attr.hidden}
|
||||
<Label label={settings.string.Hidden} />
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<Component is={view.component.BooleanTruePresenter} props={{ value: attr.isCustom ?? false }} />
|
||||
</td>
|
||||
{#if attributeMapper}
|
||||
<td>
|
||||
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{#if attr.isCustom}
|
||||
<div class="trans-title p-1">
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class:accent={!attr.hidden}>
|
||||
<Label label={attr.label} />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="select-text whitespace-nowrap trans-title text-xs text-right" style:padding-right={'1rem !important'}>
|
||||
<Label label={attr.type.label} />
|
||||
{#if attrType !== undefined}
|
||||
: <Label label={attrType} />
|
||||
{/if}
|
||||
{#if attr.type._class === core.class.EnumOf}
|
||||
{#await getEnumName(attr.type) then name}
|
||||
{#if name}
|
||||
: {name}
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</td>
|
||||
{#if attributeMapper}
|
||||
<td>
|
||||
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
{#if attributes.length === 0}
|
||||
<tr class="antiTable-body__row">
|
||||
<td>
|
||||
<div class="trans-title">
|
||||
{#if clazz}
|
||||
<Label label={clazz.label} />
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td class="select-text whitespace-nowrap"> </td>
|
||||
<td> </td>
|
||||
{#if attributeMapper}
|
||||
<td> </td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/if}
|
||||
|
@ -15,8 +15,9 @@
|
||||
<script lang="ts">
|
||||
import { Class, ClassifierKind, Doc, Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getEventPositionElement, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import { Icon, IconAdd, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||
import { ContextMenu } from '@hcengineering/view-resources'
|
||||
import ObjectPresenter from '@hcengineering/view-resources/src/components/ObjectPresenter.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import settings from '../plugin'
|
||||
|
||||
@ -25,10 +26,10 @@
|
||||
export let ofClass: Ref<Class<Doc>> | undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getDescendants (_class: Ref<Class<Doc>>): Ref<Class<Doc>>[] {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const result: Ref<Class<Doc>>[] = []
|
||||
const desc = hierarchy.getDescendants(_class)
|
||||
const vars = [ClassifierKind.MIXIN]
|
||||
@ -55,7 +56,7 @@
|
||||
</script>
|
||||
|
||||
{#each classes as cl}
|
||||
{@const clazz = hierarchy.getClass(cl)}
|
||||
{@const clazz = client.getHierarchy().getClass(cl)}
|
||||
{@const desc = getDescendants(cl)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
@ -69,16 +70,18 @@
|
||||
showContextMenu(evt, clazz)
|
||||
}}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-row-center">
|
||||
{#if clazz.icon}
|
||||
<div class="mr-2 flex">
|
||||
<div class="mr-1 flex">
|
||||
<Icon icon={clazz.icon} size={'medium'} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && client.getHierarchy().hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<Icon icon={IconAdd} size={'x-small'} fill={'var(--theme-dark-color)'} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<span class="overflow-label caption-color"><Label label={clazz.label} /></span>
|
||||
<span class="overflow-label caption-color">
|
||||
<ObjectPresenter _class={clazz._class} objectId={clazz._id} value={clazz} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if desc.length}
|
||||
|
@ -16,21 +16,22 @@
|
||||
import core, { Class, Doc, Obj, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { AnySvelteComponent, getLocation, Icon, Label, navigate } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, Icon, Label, getLocation, navigate } from '@hcengineering/ui'
|
||||
import setting from '../plugin'
|
||||
import { filterDescendants } from '../utils'
|
||||
import ClassAttributes from './ClassAttributes.svelte'
|
||||
import ClassHierarchy from './ClassHierarchy.svelte'
|
||||
|
||||
export let ofClass: Ref<Class<Obj>> | undefined
|
||||
export let ofClass: Ref<Class<Obj>> | undefined = undefined
|
||||
export let attributeMapper:
|
||||
| {
|
||||
component: AnySvelteComponent
|
||||
label: IntlString
|
||||
props: Record<string, any>
|
||||
}
|
||||
| undefined
|
||||
| undefined = undefined
|
||||
export let withoutHeader = false
|
||||
export let useOfClassAttributes = true
|
||||
|
||||
const loc = getLocation()
|
||||
const client = getClient()
|
||||
@ -47,26 +48,38 @@
|
||||
const clQuery = createQuery()
|
||||
|
||||
let classes: Ref<Class<Doc>>[] = []
|
||||
clQuery.query(core.class.Class, {}, (res) => {
|
||||
classes = filterDescendants(hierarchy, ofClass, res)
|
||||
let rawClasses: Class<Doc>[] = []
|
||||
|
||||
if (ofClass !== undefined) {
|
||||
// We need to include all possible mixins as well
|
||||
for (const ancestor of hierarchy.getAncestors(ofClass)) {
|
||||
if (ancestor === ofClass) {
|
||||
continue
|
||||
}
|
||||
const mixins = hierarchy.getDescendants(ancestor).filter((it) => hierarchy.isMixin(it))
|
||||
for (const m of mixins) {
|
||||
const mm = hierarchy.getClass(m)
|
||||
if (!classes.includes(m) && mm.extends === ancestor && mm.label !== undefined) {
|
||||
// Check if parent of
|
||||
classes.push(m)
|
||||
}
|
||||
clQuery.query(core.class.Class, {}, (res) => {
|
||||
rawClasses = res
|
||||
})
|
||||
|
||||
$: classes = filterDescendants(hierarchy, ofClass, rawClasses)
|
||||
$: if (ofClass !== undefined) {
|
||||
// We need to include all possible mixins as well
|
||||
for (const ancestor of hierarchy.getAncestors(ofClass)) {
|
||||
if (ancestor === ofClass) {
|
||||
continue
|
||||
}
|
||||
const mixins = hierarchy.getDescendants(ancestor).filter((it) => hierarchy.isMixin(it))
|
||||
for (const m of mixins) {
|
||||
const mm = hierarchy.getClass(m)
|
||||
if (
|
||||
!classes.includes(m) &&
|
||||
mm.extends === ancestor &&
|
||||
mm.label !== undefined &&
|
||||
client.getHierarchy().hasMixin(mm, setting.mixin.Editable)
|
||||
) {
|
||||
// Check if parent of
|
||||
classes.push(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$: if (ofClass !== undefined && _class !== undefined && !client.getHierarchy().isDerived(_class, ofClass)) {
|
||||
_class = ofClass
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
@ -91,7 +104,11 @@
|
||||
</div>
|
||||
<div class="ac-column max">
|
||||
{#if _class !== undefined}
|
||||
<ClassAttributes {_class} {ofClass} {attributeMapper} />
|
||||
<table class="antiTable">
|
||||
<tbody>
|
||||
<ClassAttributes {_class} {ofClass} {attributeMapper} {useOfClassAttributes} />
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -95,6 +95,8 @@
|
||||
index = e.detail?.index
|
||||
defaultValue = e.detail?.defaultValue
|
||||
}
|
||||
|
||||
$: clazz = client.getHierarchy().getClass(_class)
|
||||
</script>
|
||||
|
||||
<Card
|
||||
@ -106,6 +108,13 @@
|
||||
}}
|
||||
on:changeContent
|
||||
>
|
||||
<svelte:fragment slot="title">
|
||||
<div class="flex-row-center">
|
||||
<Label label={setting.string.CreatingAttribute} />
|
||||
<div class="p-1">></div>
|
||||
<Label label={clazz.label} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="mb-2"><EditBox bind:value={name} placeholder={core.string.Name} /></div>
|
||||
<div class="flex-col mb-2">
|
||||
<div class="flex-row-center flex-grow">
|
||||
|
@ -35,7 +35,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
async function setInviteSettings () {
|
||||
async function setInviteSettings (): Promise<void> {
|
||||
const newSettings = {
|
||||
expirationTime: expTime,
|
||||
emailMask: mask,
|
||||
|
@ -81,7 +81,7 @@
|
||||
{items}
|
||||
selected={account.role?.toString()}
|
||||
on:selected={(e) => {
|
||||
change(account, Number(e.detail))
|
||||
void change(account, Number(e.detail))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -100,7 +100,7 @@
|
||||
{disabled}
|
||||
kind={'primary'}
|
||||
on:click={() => {
|
||||
save()
|
||||
void save()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -22,6 +22,7 @@
|
||||
import { Button, createFocusManager, EditBox, FocusHandler, Icon, Label, showPopup } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let avatarEditor: EditableAvatar
|
||||
@ -34,17 +35,17 @@
|
||||
onDestroy(
|
||||
employeeByIdStore.subscribe((p) => {
|
||||
const emp = p.get(account.person as Ref<Employee>)
|
||||
if (emp) {
|
||||
if (emp !== undefined) {
|
||||
firstName = getFirstName(emp.name)
|
||||
lastName = getLastName(emp.name)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
async function onAvatarDone (e: any) {
|
||||
async function onAvatarDone (e: any): Promise<void> {
|
||||
if (employee === undefined) return
|
||||
|
||||
if (employee.avatar) {
|
||||
if (employee.avatar != null) {
|
||||
await avatarEditor.removeAvatar(employee.avatar)
|
||||
}
|
||||
const avatar = await avatarEditor.createAvatar()
|
||||
@ -72,9 +73,9 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function nameChange () {
|
||||
if (employee) {
|
||||
await client.update(employee, {
|
||||
async function nameChange (): Promise<void> {
|
||||
if (employee !== undefined) {
|
||||
await client.diffUpdate(employee, {
|
||||
name: combineName(firstName, lastName)
|
||||
})
|
||||
}
|
||||
@ -142,7 +143,7 @@
|
||||
icon={setting.icon.Signout}
|
||||
label={setting.string.Leave}
|
||||
on:click={() => {
|
||||
leave()
|
||||
void leave()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -71,8 +71,12 @@
|
||||
}
|
||||
function selectCategory (id: string): void {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[3] = id
|
||||
loc.path.length = 4
|
||||
if (loc.path[3] === id) {
|
||||
loc.path.length = 3
|
||||
} else {
|
||||
loc.path[3] = id
|
||||
loc.path.length = 4
|
||||
}
|
||||
navigate(loc)
|
||||
}
|
||||
function signOut (): void {
|
||||
@ -86,7 +90,7 @@
|
||||
setMetadata(presentation.metadata.Token, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||
closeClient()
|
||||
void closeClient()
|
||||
navigate({ path: [loginId] })
|
||||
}
|
||||
function selectWorkspace (): void {
|
||||
@ -105,20 +109,43 @@
|
||||
<div class="antiPanel-wrap__content">
|
||||
<NavHeader label={setting.string.Settings} />
|
||||
|
||||
<Scroller shrink>
|
||||
{#each categories as category, i}
|
||||
{#if i > 0 && categories[i - 1].group !== category.group}
|
||||
<Scroller>
|
||||
{#each categories as _category, i}
|
||||
{#if i > 0 && categories[i - 1].group !== _category.group}
|
||||
<div class="antiNav-divider short line" />
|
||||
{/if}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
expandable={category._id === setting.ids.Setting}
|
||||
icon={_category.icon}
|
||||
label={_category.label}
|
||||
selected={_category.name === categoryId}
|
||||
expandable={_category.expandable ?? _category._id === setting.ids.Setting}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
selectCategory(_category.name)
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<svelte:fragment slot="tools">
|
||||
{#if _category.extraComponents?.tools}
|
||||
<Component
|
||||
is={_category.extraComponents?.tools}
|
||||
props={{
|
||||
visibleNav,
|
||||
kind: 'tools',
|
||||
categoryName: _category.name
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CategoryElement>
|
||||
{#if _category.extraComponents?.navigation}
|
||||
<Component
|
||||
is={_category.extraComponents?.navigation}
|
||||
props={{
|
||||
visibleNav,
|
||||
kind: 'navigation',
|
||||
categoryName: _category.name
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
|
@ -17,21 +17,12 @@
|
||||
import { AccountRole, getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import setting, { SettingsCategory } from '@hcengineering/setting'
|
||||
import {
|
||||
Component,
|
||||
Label,
|
||||
Location,
|
||||
Scroller,
|
||||
Separator,
|
||||
defineSeparators,
|
||||
getCurrentResolvedLocation,
|
||||
navigate,
|
||||
resolvedLocationStore,
|
||||
settingsSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import { Component, Location, getCurrentResolvedLocation, navigate, resolvedLocationStore } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import CategoryElement from './CategoryElement.svelte'
|
||||
|
||||
export let kind: 'navigation' | undefined
|
||||
export let categoryName: string
|
||||
let category: SettingsCategory | undefined
|
||||
let categoryId: string = ''
|
||||
|
||||
@ -66,42 +57,38 @@
|
||||
|
||||
function selectCategory (id: string): void {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[4] = id
|
||||
loc.path.length = 5
|
||||
loc.path[3] = categoryName
|
||||
if (loc.path[4] === id) {
|
||||
loc.path.length = 4
|
||||
} else {
|
||||
loc.path[4] = id
|
||||
loc.path.length = 5
|
||||
}
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
defineSeparators('settingWorkspace', settingsSeparators)
|
||||
</script>
|
||||
|
||||
<div class="flex h-full clear-mins">
|
||||
{#if visibleNav}
|
||||
<div class="antiPanel-navigator filledNav indent">
|
||||
<div class="antiPanel-wrap__content">
|
||||
<div class="antiNav-header overflow-label">
|
||||
<Label label={setting.string.WorkspaceSetting} />
|
||||
</div>
|
||||
<Scroller shrink>
|
||||
{#each categories as category}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
</div>
|
||||
{#if kind === 'navigation'}
|
||||
<div class="ml-4 mt-2">
|
||||
<div class="antiPanel-element">
|
||||
{#each categories as category}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{#if category.name === categoryId && category.extraComponents?.navigation}
|
||||
<Component
|
||||
is={category.extraComponents?.navigation}
|
||||
props={{ kind: 'navigation', categoryName: categoryId }}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<Separator name={'settingWorkspace'} index={0} color={'var(--theme-navpanel-border)'} />
|
||||
{/if}
|
||||
|
||||
<div class="antiPanel-component filled">
|
||||
{#if category}
|
||||
<Component is={category.component} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else if category}
|
||||
<Component is={category.component} props={{ kind: 'content' }} />
|
||||
{/if}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user