Fix statuses (#3666)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-09-07 00:49:53 +06:00 committed by GitHub
parent 47e39eae75
commit f3c3541964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 390 additions and 125 deletions

View File

@ -35,12 +35,13 @@ import contact from '@hcengineering/model-contact'
import core, { TDoc, TType } from '@hcengineering/model-core' import core, { TDoc, TType } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference' import preference, { TPreference } from '@hcengineering/model-preference'
import tags from '@hcengineering/model-tags' import tags from '@hcengineering/model-tags'
import task, { TSpaceWithStates, TTask } from '@hcengineering/model-task' import task, { TSpaceWithStates, TTask, actionTemplates as taskActionTemplates } from '@hcengineering/model-task'
import view, { actionTemplates, createAction, actionTemplates as viewTemplates } from '@hcengineering/model-view' import view, { actionTemplates, createAction, actionTemplates as viewTemplates } from '@hcengineering/model-view'
import workbench, { Application } from '@hcengineering/model-workbench' import workbench, { Application } from '@hcengineering/model-workbench'
import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import type { AnyComponent } from '@hcengineering/ui' import type { AnyComponent } from '@hcengineering/ui'
import board from './plugin' import board from './plugin'
import { State } from '@hcengineering/task'
export { boardId } from '@hcengineering/board' export { boardId } from '@hcengineering/board'
export { boardOperation } from './migration' export { boardOperation } from './migration'
@ -99,6 +100,9 @@ export class TCard extends TTask implements Card {
@Prop(TypeDate(), task.string.StartDate) @Prop(TypeDate(), task.string.StartDate)
startDate!: Timestamp | null startDate!: Timestamp | null
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: board.attribute.State })
declare status: Ref<State>
} }
@Model(board.class.MenuPage, core.class.Doc, DOMAIN_MODEL) @Model(board.class.MenuPage, core.class.Doc, DOMAIN_MODEL)
@ -167,6 +171,27 @@ export function createModel (builder: Builder): void {
board.app.Board board.app.Board
) )
createAction(
builder,
{
...taskActionTemplates.editStatus,
target: board.class.Board,
actionProps: {
ofAttribute: board.attribute.State,
doneOfAttribute: board.attribute.DoneState
},
query: {
archived: false
},
context: {
mode: ['context', 'browser'],
group: 'edit'
},
override: [task.action.EditStatuses]
},
board.action.EditStatuses
)
// const leadLookup: Lookup<Card> = // const leadLookup: Lookup<Card> =
// { // {
// state: task.class.State, // state: task.class.State,

View File

@ -22,7 +22,7 @@ import task, { KanbanTemplate, createStates } from '@hcengineering/task'
import board from './plugin' import board from './plugin'
async function createSpace (tx: TxOperations): Promise<void> { async function createSpace (tx: TxOperations): Promise<void> {
const currentTemplate = await tx.findOne(core.class.Space, { const currentTemplate = await tx.findOne(task.class.KanbanTemplateSpace, {
_id: board.space.BoardTemplates _id: board.space.BoardTemplates
}) })
if (currentTemplate === undefined) { if (currentTemplate === undefined) {
@ -36,10 +36,17 @@ async function createSpace (tx: TxOperations): Promise<void> {
private: false, private: false,
archived: false, archived: false,
members: [], members: [],
attachedToClass: board.class.Board attachedToClass: board.class.Board,
ofAttribute: board.attribute.State,
doneAttribute: board.attribute.DoneState
}, },
board.space.BoardTemplates board.space.BoardTemplates
) )
} else if (currentTemplate.ofAttribute === undefined) {
await tx.update(currentTemplate, {
ofAttribute: board.attribute.State,
doneAttribute: board.attribute.DoneState
})
} }
const current = await tx.findOne(core.class.Space, { const current = await tx.findOne(core.class.Space, {
@ -47,7 +54,7 @@ async function createSpace (tx: TxOperations): Promise<void> {
}) })
if (current === undefined) { if (current === undefined) {
const defaultTmpl = await createDefaultKanbanTemplate(tx) const defaultTmpl = await createDefaultKanbanTemplate(tx)
const [states, doneStates] = await createStates(tx, defaultTmpl) const [states, doneStates] = await createStates(tx, board.attribute.State, board.attribute.DoneState, defaultTmpl)
await tx.createDoc( await tx.createDoc(
board.class.Board, board.class.Board,
core.space.Space, core.space.Space,
@ -77,13 +84,18 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise<Ref<Kanba
] ]
} }
return await createKanbanTemplate(tx, { return await createKanbanTemplate(
kanbanId: board.template.DefaultBoard, tx,
space: board.space.BoardTemplates, {
title: 'Default board', kanbanId: board.template.DefaultBoard,
states: defaultKanban.states, space: board.space.BoardTemplates,
doneStates: defaultKanban.doneStates title: 'Default board',
}) states: defaultKanban.states,
doneStates: defaultKanban.doneStates
},
board.attribute.State,
board.attribute.DoneState
)
} }
async function createDefaults (tx: TxOperations): Promise<void> { async function createDefaults (tx: TxOperations): Promise<void> {
await createSpace(tx) await createSpace(tx)

View File

@ -62,6 +62,7 @@ export default mergeIds(boardId, board, {
ConfigDescription: '' as IntlString ConfigDescription: '' as IntlString
}, },
action: { action: {
EditStatuses: '' as Ref<Action>,
ConvertToCard: '' as Ref<Action> ConvertToCard: '' as Ref<Action>
}, },
actionImpl: { actionImpl: {

View File

@ -81,7 +81,7 @@ export class TLead extends TTask implements Lead {
@Prop(TypeRef(contact.mixin.Employee), lead.string.Assignee) @Prop(TypeRef(contact.mixin.Employee), lead.string.Assignee)
declare assignee: Ref<Employee> | null declare assignee: Ref<Employee> | null
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: task.attribute.State }) @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: lead.attribute.State })
declare status: Ref<State> declare status: Ref<State>
declare space: Ref<Funnel> declare space: Ref<Funnel>
@ -365,6 +365,27 @@ export function createModel (builder: Builder): void {
} }
} }
createAction(
builder,
{
...actionTemplates.editStatus,
target: lead.class.Funnel,
actionProps: {
ofAttribute: lead.attribute.State,
doneOfAttribute: lead.attribute.DoneState
},
query: {
archived: false
},
context: {
mode: ['context', 'browser'],
group: 'edit'
},
override: [task.action.EditStatuses]
},
lead.action.EditStatuses
)
builder.createDoc( builder.createDoc(
notification.class.NotificationGroup, notification.class.NotificationGroup,
core.space.Model, core.space.Model,

View File

@ -22,7 +22,7 @@ import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
import lead from './plugin' import lead from './plugin'
async function createSpace (tx: TxOperations): Promise<void> { async function createSpace (tx: TxOperations): Promise<void> {
const currentTemplate = await tx.findOne(core.class.Space, { const currentTemplate = await tx.findOne(task.class.KanbanTemplateSpace, {
_id: lead.space.FunnelTemplates _id: lead.space.FunnelTemplates
}) })
if (currentTemplate === undefined) { if (currentTemplate === undefined) {
@ -36,10 +36,17 @@ async function createSpace (tx: TxOperations): Promise<void> {
private: false, private: false,
members: [], members: [],
archived: false, archived: false,
attachedToClass: lead.class.Funnel attachedToClass: lead.class.Funnel,
ofAttribute: lead.attribute.State,
doneAttribute: lead.attribute.DoneState
}, },
lead.space.FunnelTemplates lead.space.FunnelTemplates
) )
} else if (currentTemplate.ofAttribute === undefined) {
await tx.update(currentTemplate, {
ofAttribute: lead.attribute.State,
doneAttribute: lead.attribute.DoneState
})
} }
const current = await tx.findOne(core.class.Space, { const current = await tx.findOne(core.class.Space, {
@ -47,7 +54,7 @@ async function createSpace (tx: TxOperations): Promise<void> {
}) })
if (current === undefined) { if (current === undefined) {
const defaultTmpl = await createDefaultKanbanTemplate(tx) const defaultTmpl = await createDefaultKanbanTemplate(tx)
const [states, doneStates] = await createStates(tx, defaultTmpl) const [states, doneStates] = await createStates(tx, lead.attribute.State, lead.attribute.DoneState, defaultTmpl)
await tx.createDoc( await tx.createDoc(
lead.class.Funnel, lead.class.Funnel,
core.space.Space, core.space.Space,
@ -81,13 +88,18 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise<Ref<Kanba
] ]
} }
return await createKanbanTemplate(tx, { return await createKanbanTemplate(
kanbanId: lead.template.DefaultFunnel, tx,
space: lead.space.FunnelTemplates, {
title: 'Default funnel', kanbanId: lead.template.DefaultFunnel,
states: defaultKanban.states, space: lead.space.FunnelTemplates,
doneStates: defaultKanban.doneStates title: 'Default funnel',
}) states: defaultKanban.states,
doneStates: defaultKanban.doneStates
},
lead.attribute.State,
lead.attribute.DoneState
)
} }
async function fixTemplateSpace (tx: TxOperations): Promise<void> { async function fixTemplateSpace (tx: TxOperations): Promise<void> {

View File

@ -60,6 +60,7 @@ export default mergeIds(leadId, lead, {
Lead: '' as Ref<ActionCategory> Lead: '' as Ref<ActionCategory>
}, },
action: { action: {
EditStatuses: '' as Ref<Action>,
CreateGlobalLead: '' as Ref<Action> CreateGlobalLead: '' as Ref<Action>
}, },
ids: { ids: {

View File

@ -165,7 +165,7 @@ export class TApplicant extends TTask implements Applicant {
@Prop(TypeRef(contact.mixin.Employee), recruit.string.AssignedRecruiter) @Prop(TypeRef(contact.mixin.Employee), recruit.string.AssignedRecruiter)
declare assignee: Ref<Employee> | null declare assignee: Ref<Employee> | null
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: task.attribute.State }) @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: recruit.attribute.State })
declare status: Ref<State> declare status: Ref<State>
} }
@ -368,6 +368,27 @@ export function createModel (builder: Builder): void {
recruit.app.Recruit recruit.app.Recruit
) )
createAction(
builder,
{
...actionTemplates.editStatus,
target: recruit.class.Vacancy,
actionProps: {
ofAttribute: recruit.attribute.State,
doneOfAttribute: recruit.attribute.DoneState
},
query: {
archived: false
},
context: {
mode: ['context', 'browser'],
group: 'edit'
},
override: [task.action.EditStatuses]
},
recruit.action.EditStatuses
)
builder.createDoc( builder.createDoc(
view.class.Viewlet, view.class.Viewlet,
core.space.Model, core.space.Model,

View File

@ -92,15 +92,20 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise<Ref<Kanba
] ]
} }
return await createKanbanTemplate(tx, { return await createKanbanTemplate(
kanbanId: recruit.template.DefaultVacancy, tx,
space: recruit.space.VacancyTemplates as Ref<Doc> as Ref<Space>, {
title: 'Default vacancy', kanbanId: recruit.template.DefaultVacancy,
description: '', space: recruit.space.VacancyTemplates as Ref<Doc> as Ref<Space>,
shortDescription: '', title: 'Default vacancy',
states: defaultKanban.states, description: '',
doneStates: defaultKanban.doneStates shortDescription: '',
}) states: defaultKanban.states,
doneStates: defaultKanban.doneStates
},
recruit.attribute.State,
recruit.attribute.DoneState
)
} }
async function createSpaces (tx: TxOperations): Promise<void> { async function createSpaces (tx: TxOperations): Promise<void> {
@ -142,7 +147,7 @@ async function createSpaces (tx: TxOperations): Promise<void> {
await tx.update(currentReviews, { private: false }) await tx.update(currentReviews, { private: false })
} }
const currentTemplate = await tx.findOne(core.class.Space, { const currentTemplate = await tx.findOne(task.class.KanbanTemplateSpace, {
_id: recruit.space.VacancyTemplates _id: recruit.space.VacancyTemplates
}) })
if (currentTemplate === undefined) { if (currentTemplate === undefined) {
@ -157,9 +162,16 @@ async function createSpaces (tx: TxOperations): Promise<void> {
private: false, private: false,
members: [], members: [],
archived: false, archived: false,
attachedToClass: recruit.class.Vacancy attachedToClass: recruit.class.Vacancy,
ofAttribute: recruit.attribute.State,
doneAttribute: recruit.attribute.DoneState
}, },
recruit.space.VacancyTemplates recruit.space.VacancyTemplates
) )
} else if (currentTemplate.ofAttribute === undefined) {
await tx.update(currentTemplate, {
ofAttribute: recruit.attribute.State,
doneAttribute: recruit.attribute.DoneState
})
} }
} }

View File

@ -31,7 +31,8 @@ export default mergeIds(recruitId, recruit, {
CopyApplicationLink: '' as Ref<Action>, CopyApplicationLink: '' as Ref<Action>,
CopyCandidateLink: '' as Ref<Action>, CopyCandidateLink: '' as Ref<Action>,
MoveApplicant: '' as Ref<Action>, MoveApplicant: '' as Ref<Action>,
GetTalentIds: '' as Ref<Action> GetTalentIds: '' as Ref<Action>,
EditStatuses: '' as Ref<Action>
}, },
actionImpl: { actionImpl: {
CreateOpinion: '' as ViewAction, CreateOpinion: '' as ViewAction,

View File

@ -158,6 +158,8 @@ export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace
description!: IntlString description!: IntlString
icon!: AnyComponent icon!: AnyComponent
editor!: AnyComponent editor!: AnyComponent
ofAttribute!: Ref<Attribute<State>>
doneAttribute!: Ref<Attribute<DoneState>>
attachedToClass!: Ref<Class<Doc>> attachedToClass!: Ref<Class<Doc>>
} }
@ -337,6 +339,10 @@ export function createModel (builder: Builder): void {
{ {
...actionTemplates.editStatus, ...actionTemplates.editStatus,
target: task.class.SpaceWithStates, target: task.class.SpaceWithStates,
actionProps: {
ofAttribute: task.attribute.State,
doneOfAttribute: task.attribute.DoneState
},
query: { query: {
archived: false archived: false
}, },

View File

@ -14,6 +14,7 @@
// //
import { import {
Attribute,
Class, Class,
DOMAIN_STATUS, DOMAIN_STATUS,
DOMAIN_TX, DOMAIN_TX,
@ -25,13 +26,14 @@ import {
TxCollectionCUD, TxCollectionCUD,
TxCreateDoc, TxCreateDoc,
TxOperations, TxOperations,
TxUpdateDoc TxUpdateDoc,
toIdMap
} from '@hcengineering/core' } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model' import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core' import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import tags from '@hcengineering/model-tags' import tags from '@hcengineering/model-tags'
import { DOMAIN_VIEW } from '@hcengineering/model-view' import { DOMAIN_VIEW } from '@hcengineering/model-view'
import { DoneStateTemplate, KanbanTemplate, StateTemplate, Task, genRanks } from '@hcengineering/task' import { DoneState, DoneStateTemplate, KanbanTemplate, State, StateTemplate, Task, genRanks } from '@hcengineering/task'
import view, { Filter, FilteredView } from '@hcengineering/view' import view, { Filter, FilteredView } from '@hcengineering/view'
import { DOMAIN_TASK } from '.' import { DOMAIN_TASK } from '.'
import task from './plugin' import task from './plugin'
@ -73,7 +75,9 @@ export async function createSequence (tx: TxOperations, _class: Ref<Class<Doc>>)
*/ */
export async function createKanbanTemplate ( export async function createKanbanTemplate (
client: TxOperations, client: TxOperations,
data: KanbanTemplateData data: KanbanTemplateData,
ofAttribute: Ref<Attribute<Status>>,
doneAtrtribute?: Ref<Attribute<DoneState>>
): Promise<Ref<KanbanTemplate>> { ): Promise<Ref<KanbanTemplate>> {
const current = await client.findOne(task.class.KanbanTemplate, { _id: data.kanbanId }) const current = await client.findOne(task.class.KanbanTemplate, { _id: data.kanbanId })
if (current !== undefined) { if (current !== undefined) {
@ -96,7 +100,7 @@ export async function createKanbanTemplate (
data.doneStates.map((st, i) => data.doneStates.map((st, i) =>
client.createDoc(st.isWon ? task.class.WonStateTemplate : task.class.LostStateTemplate, data.space, { client.createDoc(st.isWon ? task.class.WonStateTemplate : task.class.LostStateTemplate, data.space, {
rank: doneStateRanks[i], rank: doneStateRanks[i],
ofAttribute: task.attribute.DoneState, ofAttribute: doneAtrtribute ?? ofAttribute,
name: st.name, name: st.name,
attachedTo: data.kanbanId attachedTo: data.kanbanId
}) })
@ -108,7 +112,7 @@ export async function createKanbanTemplate (
data.states.map((st, i) => data.states.map((st, i) =>
client.createDoc(task.class.StateTemplate, data.space, { client.createDoc(task.class.StateTemplate, data.space, {
attachedTo: data.kanbanId, attachedTo: data.kanbanId,
ofAttribute: task.attribute.State, ofAttribute,
rank: stateRanks[i], rank: stateRanks[i],
name: st.name, name: st.name,
color: st.color color: st.color
@ -226,7 +230,46 @@ async function renameStatePrefs (client: MigrationUpgradeClient): Promise<void>
} }
} }
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, { space: { $ne: task.space.Statuses } })
for (const oldStatus of oldStatuses) {
const space = map.get(oldStatus.space)
if (space !== undefined) {
try {
const isDone = oldStatus._class === task.class.DoneState
let ofAttribute = task.attribute.State
if (space._class === ('recruit:class:Vacancy' as Ref<Class<Space>>)) {
ofAttribute = isDone
? ('recruit.attribute.DoneState' as Ref<Attribute<State>>)
: ('recruit:attribute:State' as Ref<Attribute<State>>)
}
if (space._class === ('lead:class:Funnel' as Ref<Class<Space>>)) {
ofAttribute = isDone
? ('lead.attribute.DoneState' as Ref<Attribute<State>>)
: ('lead:attribute:State' as Ref<Attribute<State>>)
}
if (space._class === ('board:class:Board' as Ref<Class<Space>>)) {
ofAttribute = isDone
? ('board.attribute.DoneState' as Ref<Attribute<State>>)
: ('board:attribute:State' as Ref<Attribute<State>>)
}
if (space._class === ('tracker:class:Project' as Ref<Class<Space>>)) {
ofAttribute = 'tracker:attribute:IssueStatus' as Ref<Attribute<State>>
}
if (ofAttribute !== oldStatus.ofAttribute) {
await client.update(DOMAIN_STATUS, { _id: oldStatus._id }, { ofAttribute })
}
} catch (err) {
console.log(err)
}
}
}
}
async function migrateStatuses (client: MigrationClient): Promise<void> { async function migrateStatuses (client: MigrationClient): Promise<void> {
await fixStatusAttributes(client)
const oldStatuses = await client.find<OldStatus>(DOMAIN_STATUS, { space: { $ne: task.space.Statuses } }) const oldStatuses = await client.find<OldStatus>(DOMAIN_STATUS, { space: { $ne: task.space.Statuses } })
const newStatuses: Map<string, Status> = new Map() const newStatuses: Map<string, Status> = new Map()
const oldStatusesMap = new Map<Ref<Status>, Ref<Status>>() const oldStatusesMap = new Map<Ref<Status>, Ref<Status>>()

View File

@ -23,7 +23,12 @@ export async function createVacancy (
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true) const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
const [states, doneStates] = await createStates(client, templateId) const [states, doneStates] = await createStates(
client,
recruit.attribute.State,
recruit.attribute.DoneState,
templateId
)
const id = await client.createDoc(recruit.class.Vacancy, core.space.Space, { const id = await client.createDoc(recruit.class.Vacancy, core.space.Space, {
name, name,

View File

@ -25,7 +25,7 @@ export async function createBoard (
description: string, description: string,
templateId?: Ref<KanbanTemplate> templateId?: Ref<KanbanTemplate>
): Promise<Ref<Board>> { ): Promise<Ref<Board>> {
const [states, doneStates] = await createStates(client, templateId) const [states, doneStates] = await createStates(client, board.attribute.State, board.attribute.DoneState, templateId)
const boardRef = await client.createDoc(board.class.Board, core.space.Space, { const boardRef = await client.createDoc(board.class.Board, core.space.Space, {
name, name,

View File

@ -15,7 +15,7 @@
// //
import { Employee } from '@hcengineering/contact' import { Employee } from '@hcengineering/contact'
import type { Class, Doc, Markup, Ref, Timestamp, Type } from '@hcengineering/core' import type { Attribute, Class, Doc, Markup, Ref, Timestamp, Type } from '@hcengineering/core'
import type { Asset, IntlString, Plugin } from '@hcengineering/platform' import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform'
import type { Preference } from '@hcengineering/preference' import type { Preference } from '@hcengineering/preference'
@ -120,6 +120,10 @@ const boards = plugin(boardId, {
string: { string: {
ConfigLabel: '' as IntlString ConfigLabel: '' as IntlString
}, },
attribute: {
State: '' as Ref<Attribute<State>>,
DoneState: '' as Ref<Attribute<DoneState>>
},
icon: { icon: {
Board: '' as Asset, Board: '' as Asset,
Card: '' as Asset Card: '' as Asset

View File

@ -15,11 +15,11 @@
// //
import type { Contact } from '@hcengineering/contact' import type { Contact } from '@hcengineering/contact'
import type { Class, Doc, Ref, Timestamp } from '@hcengineering/core' import type { Attribute, Class, Doc, Ref, Timestamp } from '@hcengineering/core'
import { Mixin } from '@hcengineering/core' import { Mixin } from '@hcengineering/core'
import type { Asset, IntlString, Plugin } from '@hcengineering/platform' import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform'
import type { KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task' import type { DoneState, KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task'
/** /**
* @public * @public
@ -73,6 +73,10 @@ const lead = plugin(leadId, {
Lead: '' as IntlString, Lead: '' as IntlString,
ConfigLabel: '' as IntlString ConfigLabel: '' as IntlString
}, },
attribute: {
State: '' as Ref<Attribute<State>>,
DoneState: '' as Ref<Attribute<DoneState>>
},
icon: { icon: {
Funnel: '' as Asset, Funnel: '' as Asset,
Lead: '' as Asset, Lead: '' as Asset,

View File

@ -174,7 +174,12 @@
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true) const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
const [states, doneStates] = await createStates(client, templateId) const [states, doneStates] = await createStates(
client,
recruit.attribute.State,
recruit.attribute.DoneState,
templateId
)
const id = await client.createDoc( const id = await client.createDoc(
recruit.class.Vacancy, recruit.class.Vacancy,

View File

@ -15,11 +15,21 @@
import { Event } from '@hcengineering/calendar' import { Event } from '@hcengineering/calendar'
import type { Channel, Organization, Person } from '@hcengineering/contact' import type { Channel, Organization, Person } from '@hcengineering/contact'
import type { AttachedData, AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestamp } from '@hcengineering/core' import type {
AttachedData,
AttachedDoc,
Attribute,
Class,
Doc,
Mixin,
Ref,
Space,
Timestamp
} from '@hcengineering/core'
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform' import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform'
import { TagReference } from '@hcengineering/tags' import { TagReference } from '@hcengineering/tags'
import type { KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task' import type { DoneState, KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task'
import { AnyComponent, ResolvedLocation } from '@hcengineering/ui' import { AnyComponent, ResolvedLocation } from '@hcengineering/ui'
/** /**
@ -155,6 +165,10 @@ const recruit = plugin(recruitId, {
Candidate: '' as Ref<Mixin<Candidate>>, Candidate: '' as Ref<Mixin<Candidate>>,
VacancyList: '' as Ref<Mixin<VacancyList>> VacancyList: '' as Ref<Mixin<VacancyList>>
}, },
attribute: {
State: '' as Ref<Attribute<State>>,
DoneState: '' as Ref<Attribute<DoneState>>
},
component: { component: {
EditVacancy: '' as AnyComponent EditVacancy: '' as AnyComponent
}, },

View File

@ -28,7 +28,7 @@
let templateMap = new Map<Ref<KanbanTemplate>, KanbanTemplate>() let templateMap = new Map<Ref<KanbanTemplate>, KanbanTemplate>()
const templatesQ = createQuery() const templatesQ = createQuery()
$: if (folder !== undefined) { $: if (folder !== undefined) {
templatesQ.query(task.class.KanbanTemplate, { space: folder._id as Ref<Doc> as Ref<Space> }, (result) => { templatesQ.query(task.class.KanbanTemplate, { space: folder._id }, (result) => {
templates = result templates = result
}) })
} else { } else {
@ -81,7 +81,7 @@
await Promise.all( await Promise.all(
doneStates.map(async (ds) => { doneStates.map(async (ds) => {
await client.createDoc(ds.class, space as Ref<Doc> as Ref<Space>, { await client.createDoc(ds.class, space, {
attachedTo: template, attachedTo: template,
ofAttribute: task.attribute.DoneState, ofAttribute: task.attribute.DoneState,
name: ds.name, name: ds.name,

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Class, Data, Ref } from '@hcengineering/core' import { Attribute, Class, Data, Ref, Status } from '@hcengineering/core'
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation' import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
import { DoneState, SpaceWithStates, State, createState } from '@hcengineering/task' import { DoneState, SpaceWithStates, State, createState } from '@hcengineering/task'
import { EditBox, Label } from '@hcengineering/ui' import { EditBox, Label } from '@hcengineering/ui'
@ -25,6 +25,7 @@
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
export let status: State | undefined = undefined export let status: State | undefined = undefined
export let _class: Ref<Class<State | DoneState>> | undefined = status?._class export let _class: Ref<Class<State | DoneState>> | undefined = status?._class
export let ofAttribute: Ref<Attribute<Status>>
export let value = status?.name ?? '' export let value = status?.name ?? ''
export let space: Ref<SpaceWithStates> export let space: Ref<SpaceWithStates>
@ -40,7 +41,7 @@
if (status === undefined) { if (status === undefined) {
if (!hierarchy.isDerived(_class, task.class.DoneState)) { if (!hierarchy.isDerived(_class, task.class.DoneState)) {
const newDoc: Data<State> = { const newDoc: Data<State> = {
ofAttribute: task.attribute.State, ofAttribute,
name: value.trim(), name: value.trim(),
color: 9 color: 9
} }
@ -48,7 +49,7 @@
await client.update(_space, { $push: { states: id } }) await client.update(_space, { $push: { states: id } })
} else { } else {
const newDoc: Data<DoneState> = { const newDoc: Data<DoneState> = {
ofAttribute: task.attribute.DoneState, ofAttribute,
name: value.trim() name: value.trim()
} }
const id = await createState(client, _class, newDoc) const id = await createState(client, _class, newDoc)

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Class, Data, Ref, SortingOrder } from '@hcengineering/core' import { Attribute, Class, Data, Ref, SortingOrder, Status } from '@hcengineering/core'
import presentation, { Card, getClient } from '@hcengineering/presentation' import presentation, { Card, getClient } from '@hcengineering/presentation'
import { DoneStateTemplate, KanbanTemplate, KanbanTemplateSpace, StateTemplate, calcRank } from '@hcengineering/task' import { DoneStateTemplate, KanbanTemplate, KanbanTemplateSpace, StateTemplate, calcRank } from '@hcengineering/task'
import { EditBox, Label } from '@hcengineering/ui' import { EditBox, Label } from '@hcengineering/ui'
@ -26,6 +26,7 @@
export let status: StateTemplate | undefined = undefined export let status: StateTemplate | undefined = undefined
export let _class: Ref<Class<StateTemplate | DoneStateTemplate>> | undefined = status?._class export let _class: Ref<Class<StateTemplate | DoneStateTemplate>> | undefined = status?._class
export let template: KanbanTemplate export let template: KanbanTemplate
export let ofAttribute: Ref<Attribute<Status>>
export let space: KanbanTemplateSpace export let space: KanbanTemplateSpace
export let value = status?.name ?? '' export let value = status?.name ?? ''
@ -36,14 +37,14 @@
if (_class !== undefined && status === undefined) { if (_class !== undefined && status === undefined) {
const lastOne = await client.findOne(_class, attachedTo, { sort: { rank: SortingOrder.Descending } }) const lastOne = await client.findOne(_class, attachedTo, { sort: { rank: SortingOrder.Descending } })
let newDoc: Data<StateTemplate> = { let newDoc: Data<StateTemplate> = {
ofAttribute: task.attribute.State, ofAttribute,
name: value.trim(), name: value.trim(),
rank: calcRank(lastOne, undefined), rank: calcRank(lastOne, undefined),
...attachedTo ...attachedTo
} }
if (!hierarchy.isDerived(_class, task.class.DoneState)) { if (!hierarchy.isDerived(_class, task.class.DoneState)) {
newDoc = { newDoc = {
ofAttribute: task.attribute.State, ofAttribute,
name: value.trim(), name: value.trim(),
color: 9, color: 9,
rank: calcRank(lastOne, undefined), rank: calcRank(lastOne, undefined),

View File

@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import type { Doc, DocumentQuery, IdMap, Ref, Status } from '@hcengineering/core' import type { Attribute, Doc, DocumentQuery, IdMap, Ref, Status } from '@hcengineering/core'
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation' import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
import type { DoneState, LostState, SpaceWithStates, State, WonState } from '@hcengineering/task' import type { DoneState, LostState, SpaceWithStates, State, WonState } from '@hcengineering/task'
import { Icon, Label, Panel, Scroller, showPopup } from '@hcengineering/ui' import { Icon, Label, Panel, Scroller, showPopup } from '@hcengineering/ui'
@ -24,6 +24,8 @@
import StatesEditor from './StatesEditor.svelte' import StatesEditor from './StatesEditor.svelte'
export let _id: Ref<SpaceWithStates> export let _id: Ref<SpaceWithStates>
export let ofAttribute: Ref<Attribute<Status>>
export let doneOfAttribute: Ref<Attribute<Status>>
let spaceInstance: SpaceWithStates | undefined let spaceInstance: SpaceWithStates | undefined
@ -150,13 +152,18 @@
<Scroller> <Scroller>
<div class="popupPanel-body__main-content py-10 clear-mins"> <div class="popupPanel-body__main-content py-10 clear-mins">
<StatesEditor {#if spaceInstance}
{states} <StatesEditor
{wonStates} space={_id}
{lostStates} {ofAttribute}
on:delete={(e) => deleteState(e.detail)} {doneOfAttribute}
on:move={(e) => onMove(e.detail.stateID, e.detail.position)} {states}
/> {wonStates}
{lostStates}
on:delete={(e) => deleteState(e.detail)}
on:move={(e) => onMove(e.detail.stateID, e.detail.position)}
/>
{/if}
</div> </div>
</Scroller> </Scroller>
</Panel> </Panel>

View File

@ -14,12 +14,11 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Ref } from '@hcengineering/core' import { Attribute, Ref, Status } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import type { DoneState, KanbanTemplate, KanbanTemplateSpace, State } from '@hcengineering/task' import type { DoneState, SpaceWithStates, State } from '@hcengineering/task'
import { import {
CircleButton, CircleButton,
Component,
IconAdd, IconAdd,
IconCircles, IconCircles,
IconMoreH, IconMoreH,
@ -39,8 +38,9 @@
import Won from '../icons/Won.svelte' import Won from '../icons/Won.svelte'
import StatusesPopup from './StatusesPopup.svelte' import StatusesPopup from './StatusesPopup.svelte'
export let template: KanbanTemplate | undefined = undefined export let space: Ref<SpaceWithStates>
export let space: KanbanTemplateSpace | undefined = undefined export let ofAttribute: Ref<Attribute<Status>> = task.attribute.State
export let doneOfAttribute: Ref<Attribute<Status>> = task.attribute.DoneState
export let states: State[] = [] export let states: State[] = []
export let wonStates: DoneState[] = [] export let wonStates: DoneState[] = []
export let lostStates: DoneState[] = [] export let lostStates: DoneState[] = []
@ -86,12 +86,8 @@
await client.updateDoc(state._class, state.space, state._id, { color }) await client.updateDoc(state._class, state.space, state._id, { color })
} }
const spaceEditor = (space as KanbanTemplateSpace)?.editor
</script> </script>
{#if spaceEditor}
<Component is={spaceEditor} props={{ template }} />
{/if}
<div class="flex-no-shrink flex-between trans-title uppercase"> <div class="flex-no-shrink flex-between trans-title uppercase">
<Label label={task.string.ActiveStates} /> <Label label={task.string.ActiveStates} />
<CircleButton <CircleButton
@ -102,15 +98,15 @@
task.component.CreateStatePopup, task.component.CreateStatePopup,
{ {
space, space,
template, ofAttribute,
_class: template !== undefined ? task.class.StateTemplate : task.class.State _class: task.class.State
}, },
undefined undefined
) )
}} }}
/> />
</div> </div>
<div class="flex-col mt-3"> <div class="flex-col flex-no-shrink mt-3">
{#each states as state, i} {#each states as state, i}
{@const color = getPlatformColorDef(state.color ?? getColorNumberByText(state.name), $themeStore.dark)} {@const color = getPlatformColorDef(state.color ?? getColorNumberByText(state.name), $themeStore.dark)}
{#if state} {#if state}
@ -155,7 +151,7 @@
onDelete: () => dispatch('delete', { state }), onDelete: () => dispatch('delete', { state }),
showDelete: states.length > 1, showDelete: states.length > 1,
onUpdate: () => { onUpdate: () => {
showPopup(task.component.CreateStatePopup, { status: state, template }, undefined) showPopup(task.component.CreateStatePopup, { status: state, space, ofAttribute }, undefined)
} }
}, },
eventToHTMLElement(ev), eventToHTMLElement(ev),
@ -169,7 +165,7 @@
{/if} {/if}
{/each} {/each}
</div> </div>
<div class="flex-col mt-9"> <div class="flex-col flex-no-shrink mt-9">
<div class="flex-no-shrink flex-between trans-title uppercase"> <div class="flex-no-shrink flex-between trans-title uppercase">
<Label label={task.string.DoneStatesWon} /> <Label label={task.string.DoneStatesWon} />
<CircleButton <CircleButton
@ -180,8 +176,8 @@
task.component.CreateStatePopup, task.component.CreateStatePopup,
{ {
space, space,
template, ofAttribute: doneOfAttribute,
_class: template !== undefined ? task.class.WonStateTemplate : task.class.WonState _class: task.class.WonState
}, },
undefined undefined
) )
@ -215,7 +211,11 @@
onDelete: () => dispatch('delete', { state }), onDelete: () => dispatch('delete', { state }),
showDelete: wonStates.length > 1, showDelete: wonStates.length > 1,
onUpdate: () => { onUpdate: () => {
showPopup(task.component.CreateStatePopup, { status: state, template }, undefined) showPopup(
task.component.CreateStatePopup,
{ status: state, space, ofAttribute: doneOfAttribute },
undefined
)
} }
}, },
eventToHTMLElement(ev), eventToHTMLElement(ev),
@ -230,7 +230,7 @@
{/each} {/each}
</div> </div>
</div> </div>
<div class="mt-9"> <div class="mt-9 flex-no-shrink">
<div class="flex-no-shrink flex-between trans-title uppercase"> <div class="flex-no-shrink flex-between trans-title uppercase">
<Label label={task.string.DoneStatesLost} /> <Label label={task.string.DoneStatesLost} />
<CircleButton <CircleButton
@ -241,8 +241,8 @@
task.component.CreateStatePopup, task.component.CreateStatePopup,
{ {
space, space,
template, ofAttribute: doneOfAttribute,
_class: template !== undefined ? task.class.LostStateTemplate : task.class.LostState _class: task.class.LostState
}, },
undefined undefined
) )
@ -276,7 +276,11 @@
onDelete: () => dispatch('delete', { state }), onDelete: () => dispatch('delete', { state }),
showDelete: lostStates.length > 1, showDelete: lostStates.length > 1,
onUpdate: () => { onUpdate: () => {
showPopup(task.component.CreateStatePopup, { status: state, template }, undefined) showPopup(
task.component.CreateStatePopup,
{ status: state, space, ofAttribute: doneOfAttribute },
undefined
)
} }
}, },
eventToHTMLElement(ev), eventToHTMLElement(ev),

View File

@ -53,6 +53,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy()
const elements: HTMLElement[] = [] const elements: HTMLElement[] = []
let selected: number | undefined let selected: number | undefined
@ -95,7 +96,11 @@
const spaceEditor = space.editor const spaceEditor = space.editor
function add (_class: Ref<Class<StateTemplate | DoneStateTemplate>>) { function add (_class: Ref<Class<StateTemplate | DoneStateTemplate>>) {
const ofAttribute = hierarchy.isDerived(_class, task.class.DoneStateTemplate)
? space.doneAttribute
: space.ofAttribute
showPopup(task.component.CreateStateTemplatePopup, { showPopup(task.component.CreateStateTemplatePopup, {
ofAttribute,
space, space,
template, template,
_class _class
@ -103,7 +108,10 @@
} }
function edit (status: StateTemplate) { function edit (status: StateTemplate) {
showPopup(task.component.CreateStateTemplatePopup, { status, template, space }) const ofAttribute = hierarchy.isDerived(status._class, task.class.DoneStateTemplate)
? space.doneAttribute
: space.ofAttribute
showPopup(task.component.CreateStateTemplatePopup, { status, template, space, ofAttribute })
} }
</script> </script>

View File

@ -14,38 +14,50 @@
// limitations under the License. // limitations under the License.
// //
import { Attribute, Ref, Status } from '@hcengineering/core'
import { Resources } from '@hcengineering/platform' import { Resources } from '@hcengineering/platform'
import { SpaceWithStates } from '@hcengineering/task' import { SpaceWithStates } from '@hcengineering/task'
import { showPopup } from '@hcengineering/ui' import { showPopup } from '@hcengineering/ui'
import AssignedTasks from './components/AssignedTasks.svelte' import AssignedTasks from './components/AssignedTasks.svelte'
import CreateStatePopup from './components/CreateStatePopup.svelte'
import CreateStateTemplatePopup from './components/CreateStateTemplatePopup.svelte'
import Dashboard from './components/Dashboard.svelte'
import DueDateEditor from './components/DueDateEditor.svelte'
import KanbanTemplatePresenter from './components/KanbanTemplatePresenter.svelte'
import StatusTableView from './components/StatusTableView.svelte'
import TaskHeader from './components/TaskHeader.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import KanbanTemplateEditor from './components/kanban/KanbanTemplateEditor.svelte' import KanbanTemplateEditor from './components/kanban/KanbanTemplateEditor.svelte'
import KanbanTemplateSelector from './components/kanban/KanbanTemplateSelector.svelte' import KanbanTemplateSelector from './components/kanban/KanbanTemplateSelector.svelte'
import KanbanView from './components/kanban/KanbanView.svelte' import KanbanView from './components/kanban/KanbanView.svelte'
import DoneStateEditor from './components/state/DoneStateEditor.svelte' import DoneStateEditor from './components/state/DoneStateEditor.svelte'
import DoneStatePresenter from './components/state/DoneStatePresenter.svelte' import DoneStatePresenter from './components/state/DoneStatePresenter.svelte'
import DoneStateRefPresenter from './components/state/DoneStateRefPresenter.svelte'
import EditStatuses from './components/state/EditStatuses.svelte' import EditStatuses from './components/state/EditStatuses.svelte'
import StateEditor from './components/state/StateEditor.svelte' import StateEditor from './components/state/StateEditor.svelte'
import StatePresenter from './components/state/StatePresenter.svelte' import StatePresenter from './components/state/StatePresenter.svelte'
import StatusTableView from './components/StatusTableView.svelte' import StateRefPresenter from './components/state/StateRefPresenter.svelte'
import TaskHeader from './components/TaskHeader.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import KanbanTemplatePresenter from './components/KanbanTemplatePresenter.svelte'
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte' import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte' import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
import Todos from './components/todos/Todos.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte' import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
import Dashboard from './components/Dashboard.svelte' import Todos from './components/todos/Todos.svelte'
import DoneStateRefPresenter from './components/state/DoneStateRefPresenter.svelte'
import StateRefPresenter from './components/state/StateRefPresenter.svelte'
import DueDateEditor from './components/DueDateEditor.svelte'
import CreateStatePopup from './components/CreateStatePopup.svelte'
import CreateStateTemplatePopup from './components/CreateStateTemplatePopup.svelte'
export { default as AssigneePresenter } from './components/AssigneePresenter.svelte' export { default as AssigneePresenter } from './components/AssigneePresenter.svelte'
export { StateRefPresenter } export { StateRefPresenter }
async function editStatuses (object: SpaceWithStates): Promise<void> { async function editStatuses (
showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'float') object: SpaceWithStates,
ev: Event,
props: {
ofAttribute: Ref<Attribute<Status>>
doneOfAttribute: Ref<Attribute<Status>>
}
): Promise<void> {
showPopup(
EditStatuses,
{ _id: object._id, ofAttribute: props.ofAttribute, doneOfAttribute: props.doneOfAttribute },
'float'
)
} }
export type StatesBarPosition = 'start' | 'middle' | 'end' | undefined export type StatesBarPosition = 'start' | 'middle' | 'end' | undefined

View File

@ -158,6 +158,8 @@ export interface KanbanTemplateSpace extends Space {
name: IntlString name: IntlString
description: IntlString description: IntlString
icon: AnyComponent icon: AnyComponent
ofAttribute: Ref<Attribute<State>>
doneAttribute?: Ref<Attribute<DoneState>>
editor?: AnyComponent editor?: AnyComponent
attachedToClass: Ref<Class<Doc>> attachedToClass: Ref<Class<Doc>>
} }

View File

@ -13,7 +13,17 @@
// limitations under the License. // limitations under the License.
// //
import { Class, Data, DocumentQuery, IdMap, Ref, SortingOrder, Status, TxOperations } from '@hcengineering/core' import {
Attribute,
Class,
Data,
DocumentQuery,
IdMap,
Ref,
SortingOrder,
Status,
TxOperations
} from '@hcengineering/core'
import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank' import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket' import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
import task, { DoneState, DoneStateTemplate, KanbanTemplate, SpaceWithStates, State } from '.' import task, { DoneState, DoneStateTemplate, KanbanTemplate, SpaceWithStates, State } from '.'
@ -86,11 +96,13 @@ export async function createState<T extends Status> (
*/ */
export async function createStates ( export async function createStates (
client: TxOperations, client: TxOperations,
ofAttribute: Ref<Attribute<Status>>,
doneAtrtribute?: Ref<Attribute<DoneState>>,
templateId?: Ref<KanbanTemplate> templateId?: Ref<KanbanTemplate>
): Promise<[Ref<Status>[], Ref<DoneState>[]]> { ): Promise<[Ref<Status>[], Ref<DoneState>[]]> {
if (templateId === undefined) { if (templateId === undefined) {
const state = await createState(client, task.class.State, { const state = await createState(client, task.class.State, {
ofAttribute: task.attribute.State, ofAttribute,
name: 'New State', name: 'New State',
color: 9 color: 9
}) })
@ -99,13 +111,13 @@ export async function createStates (
doneStates.push( doneStates.push(
await createState(client, task.class.WonState, { await createState(client, task.class.WonState, {
ofAttribute: task.attribute.DoneState, ofAttribute: doneAtrtribute ?? ofAttribute,
name: 'Won' name: 'Won'
}) })
) )
doneStates.push( doneStates.push(
await createState(client, task.class.LostState, { await createState(client, task.class.LostState, {
ofAttribute: task.attribute.DoneState, ofAttribute: doneAtrtribute ?? ofAttribute,
name: 'Lost' name: 'Lost'
}) })
) )
@ -131,7 +143,7 @@ export async function createStates (
for (const state of tmplStates) { for (const state of tmplStates) {
states.push( states.push(
await createState(client, task.class.State, { await createState(client, task.class.State, {
ofAttribute: task.attribute.State, ofAttribute,
color: state.color, color: state.color,
description: state.description, description: state.description,
name: state.name name: state.name
@ -157,7 +169,7 @@ export async function createStates (
doneStates.push( doneStates.push(
await createState(client, cl, { await createState(client, cl, {
ofAttribute: task.attribute.DoneState, ofAttribute: doneAtrtribute ?? ofAttribute,
description: state.description, description: state.description,
name: state.name name: state.name
}) })

View File

@ -55,7 +55,6 @@
clip-rule="evenodd" clip-rule="evenodd"
d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15Z" d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15Z"
/> />
{#if statusIcon.count && statusIcon.index} {#if statusIcon.count && statusIcon.index}
<path <path
d="M 4.5 4.5 L 9 4.5 A 4.5 4.5 0 {statusIcon.index > (statusIcon.count - 1) / 2 ? 1 : 0} 1 {Math.cos( d="M 4.5 4.5 L 9 4.5 A 4.5 4.5 0 {statusIcon.index > (statusIcon.count - 1) / 2 ? 1 : 0} 1 {Math.cos(
@ -66,7 +65,11 @@
transform="translate(3.5,3.5)" transform="translate(3.5,3.5)"
/> />
{:else} {:else}
<circle cx="8" cy="8" r="4" fill="var(--theme-error-color)" opacity=".15" /> <path
d="M 4.5 4.5 L 9 4.5 A 4.5 4.5 0 1 1 {Math.cos(Math.PI - 0.01) * 4.5 + 4.5} {Math.sin(Math.PI - 0.01) * 4.5 +
4.5} Z"
transform="translate(3.5,3.5)"
/>
{/if} {/if}
{:else if category._id === tracker.issueStatusCategory.Completed} {:else if category._id === tracker.issueStatusCategory.Completed}
<path <path

View File

@ -49,7 +49,7 @@
statuses = getStates(_space, $statusStore).filter((p) => p.category === tracker.issueStatusCategory.Started) statuses = getStates(_space, $statusStore).filter((p) => p.category === tracker.issueStatusCategory.Started)
} }
async function updateCategory (status: WithLookup<IssueStatus>, statuses: IssueStatus[]) { async function updateCategory (_space: Project | undefined, status: WithLookup<IssueStatus>, statuses: IssueStatus[]) {
if (status.$lookup?.category) { if (status.$lookup?.category) {
category = status.$lookup.category category = status.$lookup.category
} }
@ -68,7 +68,7 @@
} }
} }
$: updateCategory(value, statuses) $: updateCategory(_space, value, statuses)
$: icon = category?.icon $: icon = category?.icon
$: color = value.color !== undefined ? value.color : category !== undefined ? category.color : -1 $: color = value.color !== undefined ? value.color : category !== undefined ? category.color : -1
</script> </script>

View File

@ -117,7 +117,7 @@
return { return {
id: s._id, id: s._id,
component: StatusPresenter, component: StatusPresenter,
props: { value: s, size: 'small' }, props: { value: s, size: 'small', space: value.space },
isSelected: selectedStatus?._id === s._id ?? false isSelected: selectedStatus?._id === s._id ?? false
} }
}) })

View File

@ -46,7 +46,7 @@
</script> </script>
<div class="flex-presenter flex-gap-1-5"> <div class="flex-presenter flex-gap-1-5">
{#each statuses as value, i} {#each statuses as value, i (value._id)}
{#if value && i < 5} {#if value && i < 5}
<div> <div>
<IssueStatusIcon {space} {value} size={'small'} /> <IssueStatusIcon {space} {value} size={'small'} />

View File

@ -13,27 +13,27 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import core, { Doc, FindResult, IdMap, Ref, RefTo, Space, Status } from '@hcengineering/core' import core, { Doc, FindResult, IdMap, Ref, RefTo, Space, Status, toIdMap } from '@hcengineering/core'
import { translate } from '@hcengineering/platform' import { translate } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation' import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import task, { SpaceWithStates } from '@hcengineering/task' import task, { SpaceWithStates } from '@hcengineering/task'
import ui, { import ui, {
addNotification,
deviceOptionsStore,
EditWithIcon, EditWithIcon,
Icon, Icon,
IconCheck, IconCheck,
IconSearch, IconSearch,
Label, Label,
Loading, Loading,
addNotification,
deviceOptionsStore,
resizeObserver, resizeObserver,
themeStore themeStore
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { Filter } from '@hcengineering/view' import { Filter } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { buildConfigLookup, getPresenter } from '../../utils'
import view from '../../plugin'
import { FILTER_DEBOUNCE_MS, FilterRemovedNotification, sortFilterValues, statusStore } from '../..' import { FILTER_DEBOUNCE_MS, FilterRemovedNotification, sortFilterValues, statusStore } from '../..'
import view from '../../plugin'
import { buildConfigLookup, getPresenter } from '../../utils'
export let filter: Filter export let filter: Filter
export let space: Ref<Space> | undefined = undefined export let space: Ref<Space> | undefined = undefined
@ -76,11 +76,11 @@
} }
if (space !== undefined) { if (space !== undefined) {
const _space = await client.findOne(core.class.Space, { _id: space }) const _space = await client.findOne(task.class.SpaceWithStates, { _id: space as Ref<SpaceWithStates> })
if (_space) { if (_space) {
values = (_space as any)[filter.key.key] const targetClass = (filter.key.attribute.type as RefTo<Status>).to
.map((p: Ref<Status>) => statusStore.get(p)) const key = hierarchy.isDerived(targetClass, task.class.DoneState) ? 'doneStates' : 'states'
.filter((p: Status) => p !== undefined) values = (_space as any)[key].map((p: Ref<Status>) => statusStore.get(p)).filter((p: Status) => p !== undefined)
for (const value of values) { for (const value of values) {
targets.add(value?._id) targets.add(value?._id)
} }
@ -89,13 +89,14 @@
} }
} }
} else { } else {
values = [] const statuses: Status[] = []
for (const status of statusStore.values()) { for (const status of statusStore.values()) {
if (hierarchy.isDerived(status._class, targetClass)) { if (hierarchy.isDerived(status._class, targetClass) && status.ofAttribute === filter.key.attribute._id) {
values.push(status) statuses.push(status)
targets.add(status._id) targets.add(status._id)
} }
} }
values = await sort(statuses)
} }
if (targets.has(undefined)) { if (targets.has(undefined)) {
values.unshift(undefined) values.unshift(undefined)
@ -120,6 +121,26 @@
objectsPromise = undefined objectsPromise = undefined
} }
async function sort (statuses: Status[]): Promise<Status[]> {
const categories = toIdMap(await client.findAll(core.class.StatusCategory, {}))
statuses.sort((a, b) => {
if (a.category !== undefined && b.category !== undefined && a.category !== b.category) {
const aCat = categories.get(a.category)
const bCat = categories.get(b.category)
if (aCat !== undefined && bCat !== undefined) {
return aCat.order - bCat.order
}
}
if (_space != null) {
const aIndex = _space.states.findIndex((s) => s === a._id)
const bIndex = _space.states.findIndex((s) => s === b._id)
return aIndex - bIndex
}
return a.name.localeCompare(b.name)
})
return statuses
}
function isSelected (value: Doc | undefined | null, values: any[]): boolean { function isSelected (value: Doc | undefined | null, values: any[]): boolean {
return values.includes(value?._id ?? value) return values.includes(value?._id ?? value)
} }
@ -182,7 +203,14 @@
<div class="flex-row-center"> <div class="flex-row-center">
{#if value} {#if value}
{#key value._id} {#key value._id}
<svelte:component this={attribute.presenter} {value} {...attribute.props} disabled oneLine /> <svelte:component
this={attribute.presenter}
{value}
{...attribute.props}
{space}
disabled
oneLine
/>
{/key} {/key}
{:else} {:else}
<Label label={ui.string.NotSelected} /> <Label label={ui.string.NotSelected} />

View File

@ -71,7 +71,7 @@
<SearchEdit bind:value={search} on:change={() => dispatch('search', search)} /> <SearchEdit bind:value={search} on:change={() => dispatch('search', search)} />
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> --> <!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
<div class="buttons-divider" /> <div class="buttons-divider" />
<FilterButton {_class} /> <FilterButton {_class} space={spaceId} />
</div> </div>
<div class="ac-header-full medium-gap"> <div class="ac-header-full medium-gap">
<ViewletSettingButton bind:viewOptions bind:viewlet /> <ViewletSettingButton bind:viewOptions bind:viewlet />