[UBER-82] Update components (#3227)

Signed-off-by: Ruslan Bayandinov <wazsone@ya.ru>
This commit is contained in:
Ruslan Bayandinov 2023-05-22 18:03:22 +07:00 committed by GitHub
parent fb5c4b1f41
commit 2f4cb2c2a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 72 additions and 1216 deletions

View File

@ -58,7 +58,6 @@ import tags, { TagElement } from '@hcengineering/tags'
import task from '@hcengineering/task'
import {
Component,
ComponentStatus,
Issue,
IssueChildInfo,
IssueParentInfo,
@ -108,13 +107,6 @@ export function TypeIssuePriority (): Type<IssuePriority> {
@Model(tracker.class.TypeIssuePriority, core.class.Type, DOMAIN_MODEL)
export class TTypeIssuePriority extends TType {}
/**
* @public
*/
export function TypeComponentStatus (): Type<ComponentStatus> {
return { _class: tracker.class.TypeComponentStatus, label: 'TypeComponentStatus' as IntlString }
}
/**
* @public
*/
@ -122,12 +114,6 @@ export function TypeMilestoneStatus (): Type<MilestoneStatus> {
return { _class: tracker.class.TypeMilestoneStatus, label: 'TypeMilestoneStatus' as IntlString }
}
/**
* @public
*/
@Model(tracker.class.TypeComponentStatus, core.class.Type, DOMAIN_MODEL)
export class TTypeComponentStatus extends TType {}
/**
* @public
*/
@ -345,27 +331,15 @@ export class TComponent extends TDoc implements Component {
@Prop(TypeMarkup(), tracker.string.Description)
description?: Markup
@Prop(TypeComponentStatus(), tracker.string.Status)
status!: ComponentStatus
@Prop(TypeRef(contact.class.Employee), tracker.string.ComponentLead)
lead!: Ref<Employee> | null
@Prop(ArrOf(TypeRef(contact.class.Employee)), tracker.string.Members)
members!: Ref<Employee>[]
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments!: number
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.StartDate)
startDate!: Timestamp | null
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.TargetDate)
targetDate!: Timestamp | null
declare space: Ref<Project>
}
@ -466,7 +440,6 @@ export function createModel (builder: Builder): void {
TIssueTemplate,
TIssueStatus,
TTypeIssuePriority,
TTypeComponentStatus,
TMilestone,
TScrum,
TScrumRecord,
@ -930,6 +903,10 @@ export function createModel (builder: Builder): void {
presenter: tracker.component.PriorityRefPresenter
})
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.ObjectEditor, {
editor: tracker.component.EditComponent
})
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.ComponentPresenter
})
@ -969,10 +946,6 @@ export function createModel (builder: Builder): void {
value: true
})
builder.mixin(tracker.class.TypeComponentStatus, core.class.Class, view.mixin.AttributeEditor, {
inlineEditor: tracker.component.ComponentStatusEditor
})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AllValuesFunc, {
func: tracker.function.GetAllPriority
})
@ -1496,6 +1469,8 @@ export function createModel (builder: Builder): void {
attribute: 'component',
_class: tracker.class.Component,
query: {},
fillQuery: { space: 'space' },
docMatches: ['space'],
searchField: 'label',
placeholder: tracker.string.Component
},
@ -1848,7 +1823,6 @@ export function createModel (builder: Builder): void {
const componentListViewOptions: ViewOptionsModel = {
groupBy: ['lead'],
orderBy: [
['startDate', SortingOrder.Descending],
['modifiedOn', SortingOrder.Descending],
['createOn', SortingOrder.Descending]
],
@ -1863,29 +1837,12 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.List,
viewOptions: componentListViewOptions,
config: [
{ key: '', presenter: tracker.component.ComponentStatusPresenter, props: { kind: 'list', size: 'small' } },
{
key: '',
presenter: tracker.component.ComponentPresenter,
props: { kind: 'list' }
},
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
{
key: '',
presenter: contact.component.MembersPresenter,
props: {
kind: 'list',
size: 'small',
intlTitle: tracker.string.ComponentMembersTitle,
intlSearchPh: tracker.string.ComponentMembersSearchPlaceholder,
listProps: { optional: true, compression: true }
}
},
{
key: '',
presenter: tracker.component.TargetDatePresenter,
props: { listProps: { optional: true, compression: true } }
},
{ key: '', presenter: view.component.DividerPresenter, props: { type: 'divider' } },
{
key: '$lookup.lead',
@ -1901,62 +1858,4 @@ export function createModel (builder: Builder): void {
},
tracker.viewlet.ComponentList
)
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
{
label: view.string.Timeline,
icon: view.icon.Timeline,
component: tracker.component.ComponentsTimeline
},
tracker.viewlet.Timeline
)
const componentTimelineViewOptions: ViewOptionsModel = {
groupBy: [],
orderBy: [
['startDate', SortingOrder.Descending],
['modifiedOn', SortingOrder.Descending],
['createOn', SortingOrder.Descending]
],
other: [],
groupDepth: 1
}
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: tracker.class.Component,
descriptor: tracker.viewlet.Timeline,
viewOptions: componentTimelineViewOptions,
config: [
{ key: '', presenter: tracker.component.IconPresenter },
{
key: '',
presenter: tracker.component.ComponentPresenter,
props: { kind: 'list', shouldShowAvatar: false }
},
{
key: '$lookup.lead',
presenter: tracker.component.LeadPresenter,
props: { _class: tracker.class.Component, defaultClass: contact.class.Employee, shouldShowLabel: false }
},
{
key: '',
presenter: contact.component.MembersPresenter,
props: {
kind: 'link',
intlTitle: tracker.string.ComponentMembersTitle,
intlSearchPh: tracker.string.ComponentMembersSearchPlaceholder
}
},
{ key: '', presenter: tracker.component.TargetDatePresenter },
{ key: '', presenter: tracker.component.ComponentStatusPresenter, props: { width: 'min-content' } },
{ key: '', presenter: tracker.component.DeleteComponentPresenter }
]
},
tracker.viewlet.ComponentsTimeline
)
}

View File

@ -63,8 +63,7 @@ export default mergeIds(trackerId, tracker, {
IssueTemplateList: '' as Ref<Viewlet>,
IssueKanban: '' as Ref<Viewlet>,
MilestoneList: '' as Ref<Viewlet>,
ComponentList: '' as Ref<Viewlet>,
ComponentsTimeline: '' as Ref<Viewlet>
ComponentList: '' as Ref<Viewlet>
},
ids: {
TxIssueCreated: '' as Ref<TxViewlet>,

View File

@ -29,7 +29,6 @@
"CreateComponent": "Create component",
"ComponentNamePlaceholder": "Component name",
"ComponentDescriptionPlaceholder": "Description (optional)",
"ComponentStatusPlaceholder": "Change component status...",
"ComponentLead": "Lead",
"ComponentMembers": "Members",
"StartDate": "Start date",

View File

@ -29,7 +29,6 @@
"CreateComponent": "Создать компонент",
"ComponentNamePlaceholder": "Название компонента",
"ComponentDescriptionPlaceholder": "Описание (необязательно)",
"ComponentStatusPlaceholder": "Сменить статус компонента...",
"ComponentLead": "Руководитель",
"ComponentMembers": "Участники",
"StartDate": "Дата начала",

View File

@ -54,14 +54,6 @@ loadMetadata(tracker.icon, {
PriorityLow: `${icons}#priority-low`,
ComponentsList: `${icons}#list`,
ComponentsTimeline: `${icons}#timeline`,
ComponentStatusBacklog: `${icons}#component-status-backlog`,
ComponentStatusPlanned: `${icons}#component-status-planned`,
ComponentStatusInProgress: `${icons}#component-status-in-progress`,
ComponentStatusPaused: `${icons}#component-status-paused`,
ComponentStatusCompleted: `${icons}#component-status-completed`,
ComponentStatusCanceled: `${icons}#component-status-canceled`,
MilestoneStatusPlanned: `${icons}#component-status-planned`,
MilestoneStatusInProgress: `${icons}#component-status-in-progress`,
@ -72,9 +64,7 @@ loadMetadata(tracker.icon, {
CopyBranch: `${icons}#copyBranch`,
Duplicate: `${icons}#duplicate`,
TimeReport: `${icons}#timeReport`,
Estimation: `${icons}#estimation`,
Timeline: `${icons}#timeline`
Estimation: `${icons}#estimation`
})
addStringsLoader(trackerId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -28,18 +28,9 @@
ViewletSettingButton,
FilterBar
} from '@hcengineering/view-resources'
import {
Button,
IconAdd,
Label,
SearchEdit,
TabItem,
TabList,
resolvedLocationStore,
showPopup
} from '@hcengineering/ui'
import { Button, IconAdd, Label, SearchEdit, TabList, resolvedLocationStore, showPopup } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import { ComponentsFilterMode, componentsTitleMap, getIncludedComponentStatuses } from '../../utils'
import { ComponentsFilterMode, componentsTitleMap } from '../../utils'
import tracker from '../../plugin'
import ComponentsContent from './ComponentsContent.svelte'
import NewComponent from './NewComponent.svelte'
@ -53,20 +44,12 @@
const viewletQuery = createQuery()
const space = typeof query.space === 'string' ? query.space : tracker.project.DefaultProject
const filterModeList: TabItem[] = [
{ id: 'all', labelIntl: tracker.string.AllComponents, action: () => handleFilterModeChanged('all') },
{ id: 'backlog', labelIntl: tracker.string.BacklogComponents, action: () => handleFilterModeChanged('backlog') },
{ id: 'active', labelIntl: tracker.string.ActiveComponents, action: () => handleFilterModeChanged('active') },
{ id: 'closed', labelIntl: tracker.string.ClosedComponents, action: () => handleFilterModeChanged('closed') }
]
let viewlet: WithLookup<Viewlet> | undefined
let viewlets: WithLookup<Viewlet>[] | undefined
let viewletKey = makeViewletKey()
let searchQuery: DocumentQuery<Component> = { ...query }
let resultQuery: DocumentQuery<Component> = { ...searchQuery }
let includedComponentsQuery: DocumentQuery<Component>
let asideFloat = false
let asideShown = true
@ -74,19 +57,11 @@
let docWidth: number
let docSize = false
function handleFilterModeChanged (newMode: ComponentsFilterMode) {
if (newMode !== filterMode) {
filterMode = newMode
}
}
function showCreateDialog () {
showPopup(NewComponent, { space, targetElement: null }, 'top')
}
$: title = componentsTitleMap[filterMode]
$: includedComponentStatuses = getIncludedComponentStatuses(filterMode)
$: includedComponentsQuery = { status: { $in: includedComponentStatuses } }
$: searchQuery = search === '' ? { ...query } : { ...query, $search: search }
$: resultQuery = { ...searchQuery }
@ -146,9 +121,6 @@
{/if}
</div>
</div>
<div class="ac-header tabs-start full divide">
<TabList items={filterModeList} selected={filterMode} kind={'plain'} on:select={({ detail }) => detail?.action?.()} />
</div>
<FilterBar
_class={tracker.class.Component}
@ -159,7 +131,7 @@
<div class="flex w-full h-full clear-mins">
{#if viewlet}
<ComponentsContent {viewlet} query={{ ...resultQuery, ...includedComponentsQuery }} {space} {viewOptions} />
<ComponentsContent {viewlet} query={{ ...resultQuery }} {space} {viewOptions} />
{/if}
{#if $$slots.aside !== undefined && asideShown}
<div class="popupPanel-body__aside flex" class:float={asideFloat} class:shown={asideShown}>

View File

@ -15,42 +15,31 @@
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Component } from '@hcengineering/tracker'
import { Icon, getCurrentResolvedLocation, navigate, tooltip } from '@hcengineering/ui'
import { Icon, tooltip } from '@hcengineering/ui'
import tracker from '../../plugin'
import view from '@hcengineering/view'
import { DocNavLink } from '@hcengineering/view-resources'
export let value: WithLookup<Component>
export let shouldShowAvatar = true
export let onClick: (() => void) | undefined = undefined
export let disabled = false
export let inline: boolean = false
function navigateToComponent () {
if (disabled) {
return
}
if (onClick) {
onClick()
}
const loc = getCurrentResolvedLocation()
loc.path[4] = 'components'
loc.path[5] = value._id
loc.path.length = 6
loc.fragment = undefined
navigate(loc)
}
export let noUnderline = false
export let kind: 'list' | undefined = undefined
</script>
{#if value}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-presenter" class:inline-presenter={inline} on:click={navigateToComponent}>
<DocNavLink object={value} {onClick} {disabled} {noUnderline} {inline} component={view.component.EditDoc}>
<span class="flex-presenter" class:inline class:list={kind === 'list'}>
{#if !inline && shouldShowAvatar}
<div class="icon" use:tooltip={{ label: tracker.string.Component }}>
<Icon icon={tracker.icon.Component} size={'small'} />
</div>
{/if}
<span title={value.label} class="overflow-label label" class:no-underline={disabled}>
<span title={value.label} class="label nowrap">
{value.label}
</span>
</div>
</span>
</DocNavLink>
{/if}

View File

@ -1,8 +0,0 @@
<script lang="ts">
import { Component } from '@hcengineering/tracker'
import ComponentStatusPresenter from './ComponentStatusPresenter.svelte'
export let object: Component
</script>
<ComponentStatusPresenter value={object} shouldShowLabel />

View File

@ -1,54 +0,0 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Component, ComponentStatus } from '@hcengineering/tracker'
import { getClient } from '@hcengineering/presentation'
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
import tracker from '../../plugin'
import ComponentStatusSelector from './ComponentStatusSelector.svelte'
export let value: Component
export let isEditable: boolean = true
export let shouldShowLabel: boolean = false
export let kind: ButtonKind = 'link'
export let size: ButtonSize = 'large'
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = '100%'
const client = getClient()
const handleComponentStatusChanged = async (newStatus: ComponentStatus | undefined) => {
if (!isEditable || newStatus === undefined || value.status === newStatus) {
return
}
await client.update(value, { status: newStatus })
}
</script>
{#if value}
<ComponentStatusSelector
{kind}
{size}
{width}
{justify}
{isEditable}
{shouldShowLabel}
showTooltip={isEditable ? { label: tracker.string.SetStatus } : undefined}
selectedComponentStatus={value.status}
onComponentStatusChange={handleComponentStatusChanged}
/>
{/if}

View File

@ -1,83 +0,0 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { ComponentStatus } from '@hcengineering/tracker'
import { Button, showPopup, SelectPopup, eventToHTMLElement, Icon } from '@hcengineering/ui'
import type { ButtonKind, ButtonSize, LabelAndProps } from '@hcengineering/ui'
import tracker from '../../plugin'
import { defaultComponentStatuses, componentStatusAssets } from '../../utils'
export let selectedComponentStatus: ComponentStatus | undefined
export let shouldShowLabel: boolean = true
export let onComponentStatusChange: ((newComponentStatus: ComponentStatus | undefined) => void) | undefined =
undefined
export let isEditable: boolean = true
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = 'min-content'
export let showTooltip: LabelAndProps | undefined = undefined
$: selectedStatusIcon = selectedComponentStatus
? componentStatusAssets[selectedComponentStatus].icon
: tracker.icon.ComponentStatusBacklog
$: selectedStatusLabel = shouldShowLabel
? selectedComponentStatus
? componentStatusAssets[selectedComponentStatus].label
: tracker.string.Backlog
: undefined
$: statusesInfo = defaultComponentStatuses.map((s) => ({
id: s,
isSelected: componentStatusAssets[s].label === selectedStatusLabel,
...componentStatusAssets[s]
}))
const handleComponentStatusEditorOpened = (event: MouseEvent) => {
if (!isEditable) {
return
}
showPopup(
SelectPopup,
{ value: statusesInfo, placeholder: tracker.string.SetStatus, searchable: true },
eventToHTMLElement(event),
onComponentStatusChange
)
}
</script>
{#if kind === 'list'}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex-no-shrink clear-mins cursor-pointer content-pointer-events-none"
on:click={handleComponentStatusEditorOpened}
>
<Icon icon={selectedStatusIcon} {size} />
</div>
{:else}
<Button
{kind}
{size}
{width}
{justify}
disabled={!isEditable}
icon={selectedStatusIcon}
label={selectedStatusLabel}
{showTooltip}
on:click={handleComponentStatusEditorOpened}
/>
{/if}

View File

@ -13,60 +13,17 @@
// limitations under the License.
-->
<script lang="ts">
import { DocumentQuery, Ref } from '@hcengineering/core'
import { DocumentQuery } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { Component } from '@hcengineering/tracker'
import {
closePopup,
closeTooltip,
getCurrentResolvedLocation,
navigate,
resolvedLocationStore
} from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import tracker from '../../plugin'
import { ComponentsFilterMode } from '../../utils'
import ComponentBrowser from './ComponentBrowser.svelte'
import EditComponent from './EditComponent.svelte'
export let label: IntlString = tracker.string.Components
export let query: DocumentQuery<Component> = {}
export let search: string = ''
export let filterMode: ComponentsFilterMode = 'all'
let componentId: Ref<Component> | undefined
let component: Component | undefined
onDestroy(
resolvedLocationStore.subscribe(async (loc) => {
closeTooltip()
closePopup()
componentId = loc.path[5] as Ref<Component>
})
)
const componentQuery = createQuery()
$: if (componentId !== undefined) {
componentQuery.query(tracker.class.Component, { _id: componentId }, (result) => {
component = result.shift()
})
} else {
componentQuery.unsubscribe()
component = undefined
}
</script>
{#if component}
<EditComponent
{component}
on:component={(evt) => {
const loc = getCurrentResolvedLocation()
loc.path[5] = evt.detail
navigate(loc)
}}
/>
{:else}
<ComponentBrowser {label} {query} {search} {filterMode} />
{/if}
<ComponentBrowser {label} {query} {search} {filterMode} />

View File

@ -16,8 +16,7 @@
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
import { Component } from '@hcengineering/tracker'
import { Component as ViewComponent } from '@hcengineering/ui'
import { BuildModelKey, Viewlet, ViewOptions } from '@hcengineering/view'
import contact from '@hcengineering/contact'
import { Viewlet, ViewOptions } from '@hcengineering/view'
import tracker from '../../plugin'
import CreateComponent from './NewComponent.svelte'
@ -28,18 +27,6 @@
const createItemDialog = CreateComponent
const createItemLabel = tracker.string.Component
const retrieveMembers = (s: Component) => s.members
function updateConfig (config: (string | BuildModelKey)[]): (string | BuildModelKey)[] {
return config.map((it) => {
if (typeof it === 'string') {
return it
}
return it.presenter === contact.component.MembersPresenter
? { ...it, props: { ...it.props, retrieveMembers } }
: it
})
}
</script>
{#if viewlet?.$lookup?.descriptor?.component}
@ -47,7 +34,7 @@
is={viewlet.$lookup.descriptor.component}
props={{
_class: tracker.class.Component,
config: updateConfig(viewlet.config),
config: viewlet.config,
options: viewlet.options,
createItemDialog,
createItemLabel,

View File

@ -1,89 +0,0 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
import { BuildModelKey, ViewOptionModel, ViewOptions, ViewQueryOption } from '@hcengineering/view'
import {
ActionContext,
ListSelectionProvider,
SelectDirection,
selectionStore,
focusStore,
buildConfigLookup
} from '@hcengineering/view-resources'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Component } from '@hcengineering/tracker'
import { getResource } from '@hcengineering/platform'
import Timeline from './Timeline.svelte'
export let _class: Ref<Class<Component>>
export let query: DocumentQuery<Component> = {}
export let options: FindOptions<Component> | undefined = undefined
export let config: (string | BuildModelKey)[]
export let viewOptions: ViewOptions
export let viewOptionsConfig: ViewOptionModel[] | undefined = undefined
const selectionProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
if (timeline && dir === 'vertical') {
timeline.onElementSelected(offset, of)
}
})
const client = getClient()
const hierarchy = client.getHierarchy()
const componentsQuery = createQuery()
let timeline: Timeline | undefined
let components: Component[] | undefined
let resultOptions: FindOptions<Component> | undefined
let resultQuery: DocumentQuery<Component> = query
// TODO: move to "view-resources" utils
async function getResultQuery<T extends Doc> (
query: DocumentQuery<T>,
viewOptions: ViewOptionModel[] | undefined,
viewOptionsStore: ViewOptions
): Promise<DocumentQuery<T>> {
if (viewOptions === undefined) return query
let result = hierarchy.clone(query)
for (const viewOption of viewOptions) {
if (viewOption.actionTarget !== 'query') continue
const queryOption = viewOption as ViewQueryOption
const f = await getResource(queryOption.action)
result = f(viewOptionsStore[queryOption.key] ?? queryOption.defaultValue, query)
}
return result
}
$: orderBy = viewOptions.orderBy
$: lookup = buildConfigLookup(client.getHierarchy(), _class, config, options?.lookup)
$: resultOptions = { ...options, lookup, sort: { [orderBy[0]]: orderBy[1] } }
$: getResultQuery(query, viewOptionsConfig, viewOptions).then((res) => (resultQuery = { ...res, ...query }))
$: componentsQuery.query(_class, resultQuery, (result) => (components = result), resultOptions)
</script>
<ActionContext context={{ mode: 'browser' }} />
<Timeline
bind:this={timeline}
{_class}
{components}
itemsConfig={config}
options={resultOptions}
selectedObjectIds={$selectionStore ?? []}
selectedRowIndex={selectionProvider.current($focusStore)}
on:row-focus={(event) => selectionProvider.updateFocus(event.detail ?? undefined)}
on:check={(event) => selectionProvider.updateSelection(event.detail.docs, event.detail.value)}
/>

View File

@ -1,65 +1,43 @@
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import { StyledTextBox } from '@hcengineering/text-editor'
import { Component } from '@hcengineering/tracker'
import { Button, EditBox, Icon, showPopup } from '@hcengineering/ui'
import { DocAttributeBar } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy } from 'svelte'
import { activeComponent } from '../../issues'
import { EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import IssuesView from '../issues/IssuesView.svelte'
import ComponentPopup from './ComponentPopup.svelte'
import { onMount } from 'svelte'
export let component: Component
export let object: Component
const client = getClient()
const dispatch = createEventDispatcher()
async function change (field: string, value: any) {
await client.update(component, { [field]: value })
}
function selectComponent (evt: MouseEvent): void {
showPopup(ComponentPopup, { _class: tracker.class.Component }, evt.target as HTMLElement, (value) => {
if (value != null) {
component = value
dispatch('component', component._id)
}
})
let oldLabel = ''
let rawLabel = ''
function change<K extends keyof Component> (field: K, value: Component[K]) {
client.update(object, { [field]: value })
}
$: $activeComponent = component?._id
$: if (oldLabel !== object.label) {
oldLabel = object.label
rawLabel = object.label
}
onDestroy(() => {
$activeComponent = undefined
})
onMount(() => dispatch('open', { ignoreKeys: ['label'] }))
</script>
<IssuesView
query={{ component: component._id, space: component.space }}
space={component.space}
label={component.label}
>
<svelte:fragment slot="label_selector">
<Button size={'small'} kind={'link'} on:click={selectComponent}>
<svelte:fragment slot="content">
<div class="ac-header__icon"><Icon icon={tracker.icon.Component} size={'small'} /></div>
<span class="ac-header__title">{component.label}</span>
</svelte:fragment>
</Button>
</svelte:fragment>
<svelte:fragment slot="aside">
<div class="popupPanel-body__aside-content">
<EditBox kind={'large-style'} bind:value={component.label} on:change={() => change('label', component.label)} />
<div class="mt-2">
<StyledTextBox
alwaysEdit={true}
showButtons={false}
placeholder={tracker.string.Description}
content={component.description ?? ''}
on:value={(evt) => change('description', evt.detail)}
/>
</div>
</div>
<DocAttributeBar object={component} mixins={[]} ignoreKeys={['icon', 'label', 'description']} />
</svelte:fragment>
</IssuesView>
<EditBox
bind:value={rawLabel}
placeholder={tracker.string.Component}
kind="large-style"
focusable
on:blur={() => {
const trimmedLabel = rawLabel.trim()
if (trimmedLabel.length === 0) {
rawLabel = oldLabel
} else if (trimmedLabel !== object.label) {
change('label', trimmedLabel)
}
}}
/>

View File

@ -16,12 +16,11 @@
import { Data, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
import { EmployeeBox, UserBoxList } from '@hcengineering/contact-resources'
import { Component, ComponentStatus, Project } from '@hcengineering/tracker'
import { DatePresenter, EditBox } from '@hcengineering/ui'
import { EmployeeBox } from '@hcengineering/contact-resources'
import { Component, Project } from '@hcengineering/tracker'
import { EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import ComponentStatusSelector from './ComponentStatusSelector.svelte'
import { StyledTextArea } from '@hcengineering/text-editor'
export let space: Ref<Project>
@ -31,26 +30,14 @@
const object: Data<Component> = {
label: '' as IntlString,
description: '',
status: ComponentStatus.Backlog,
lead: null,
members: [],
comments: 0,
attachments: 0,
startDate: null,
targetDate: null
attachments: 0
}
async function onSave () {
await client.createDoc(tracker.class.Component, space, object)
}
const handleComponentStatusChanged = (newComponentStatus: ComponentStatus | undefined) => {
if (newComponentStatus === undefined) {
return
}
object.status = newComponentStatus
}
</script>
<Card
@ -77,12 +64,6 @@
emphasized
/>
<svelte:fragment slot="pool">
<ComponentStatusSelector
selectedComponentStatus={object.status}
onComponentStatusChange={handleComponentStatusChanged}
kind={'secondary'}
size={'large'}
/>
<EmployeeBox
label={tracker.string.ComponentLead}
placeholder={tracker.string.AssignTo}
@ -93,26 +74,5 @@
kind={'secondary'}
size={'large'}
/>
<UserBoxList
bind:items={object.members}
label={tracker.string.ComponentMembersSearchPlaceholder}
kind={'secondary'}
size={'large'}
/>
<!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate -->
<DatePresenter
bind:value={object.startDate}
labelNull={tracker.string.StartDate}
kind={'secondary'}
size={'large'}
editable
/>
<DatePresenter
bind:value={object.targetDate}
labelNull={tracker.string.TargetDate}
kind={'secondary'}
size={'large'}
editable
/>
</svelte:fragment>
</Card>

View File

@ -1,32 +0,0 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Component } from '@hcengineering/tracker'
import { getClient } from '@hcengineering/presentation'
import { ButtonKind, DueDatePresenter } from '@hcengineering/ui'
export let value: Component
export let kind: ButtonKind = 'list'
const client = getClient()
$: dueDateMs = value.targetDate
const handleDueDateChanged = async (newDate: number | null) => {
await client.update(value, { targetDate: newDate })
}
</script>
<DueDatePresenter value={dueDateMs} {kind} shouldRender onChange={handleDueDateChanged} />

View File

@ -1,490 +0,0 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, FindOptions, getObjectValue, Ref, Timestamp } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Component } from '@hcengineering/tracker'
import { CheckBox, Spinner, Timeline, TimelineRow } from '@hcengineering/ui'
import { AttributeModel, BuildModelKey } from '@hcengineering/view'
import { buildModel, LoadingProps } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import ComponentPresenter from './ComponentPresenter.svelte'
export let _class: Ref<Class<Doc>>
export let itemsConfig: (BuildModelKey | string)[]
export let selectedObjectIds: Doc[] = []
export let selectedRowIndex: number | undefined = undefined
export let components: Component[] | undefined = undefined
export let loadingProps: LoadingProps | undefined = undefined
export let options: FindOptions<Component> | undefined = undefined
const dispatch = createEventDispatcher()
const client = getClient()
$: selectedObjectIdsSet = new Set<Ref<Doc>>(selectedObjectIds.map((it) => it._id))
let selectedRows: number[] = []
$: if (selectedObjectIdsSet.size > 0 && components !== undefined) {
const tRows: number[] = []
selectedObjectIdsSet.forEach((it) => {
const index = components?.findIndex((f) => f._id === it)
if (index !== undefined) tRows.push(index)
})
selectedRows = tRows
} else selectedRows = []
export const onObjectChecked = (docs: Doc[], value: boolean) => {
dispatch('check', { docs, value })
}
const handleRowFocused = (object: Doc) => {
dispatch('row-focus', object)
}
export const onElementSelected = (offset: 1 | -1 | 0, docObject?: Doc) => {
if (!components) return
let position =
(docObject !== undefined ? components?.findIndex((x) => x._id === docObject?._id) : selectedRowIndex) ?? -1
position += offset
if (position < 0) position = 0
if (position >= components.length) position = components.length - 1
selectedRowIndex = position
handleRowFocused(components[position])
// if (objectRef) {
// objectRef.scrollIntoView({ behavior: 'auto', block: 'nearest' })
// }
}
const getLoadingElementsLength = (props: LoadingProps, options?: FindOptions<Doc>) => {
if (options?.limit && options?.limit > 0) {
return Math.min(options.limit, props.length)
}
return props.length
}
let itemModels: AttributeModel[] | undefined = undefined
$: buildModel({ client, _class, keys: itemsConfig, lookup: options?.lookup }).then((res) => (itemModels = res))
let lines: TimelineRow[] | undefined
$: lines = components?.map((proj) => {
const tR: TimelineRow = { items: [] }
tR.items = [
{
presenter: ComponentPresenter,
props: { value: proj },
startDate: proj.startDate as Timestamp,
targetDate: proj.targetDate as Timestamp
}
]
return tR
})
</script>
{#if components && itemModels && lines}
<Timeline
{lines}
{selectedRows}
selectedRow={selectedRowIndex}
on:row-focus={(ev) => {
if (ev.detail !== undefined && components !== undefined) handleRowFocused(components[ev.detail])
}}
on:check={(ev) => {
if (ev.detail !== undefined && components !== undefined) {
onObjectChecked([components[ev.detail.row]], ev.detail.value)
}
}}
>
<svelte:fragment let:row>
{#each itemModels as attributeModel, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<div class="iconPresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, components[row]) ?? ''}
object={components[row]}
{...attributeModel.props}
/>
</div>
</div>
{:else if attributeModelIndex === 1}
<div class="componentPresenter flex-grow">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, components[row]) ?? ''}
object={components[row]}
{...attributeModel.props}
/>
</div>
<div class="filler" />
{:else}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, components[row]) ?? ''}
object={components[row]}
{...attributeModel.props}
/>
</div>
{/if}
{/each}
</svelte:fragment>
</Timeline>
{:else if loadingProps !== undefined}
{#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex}
<div class="listGrid" class:fixed={rowIndex === selectedRowIndex}>
<div class="contentWrapper">
<div class="gridElement">
<CheckBox checked={false} />
<div class="ml-4">
<Spinner size="small" />
</div>
</div>
</div>
</div>
{/each}
{/if}
<style lang="scss">
.timeline-header {
display: flex;
align-items: center;
min-height: 4rem;
border-bottom: 1px solid var(--divider-color);
}
.timeline-header__title {
display: flex;
align-items: center;
flex-shrink: 0;
padding: 0 2.25rem;
height: 100%;
background-color: var(--body-accent);
box-shadow: var(--accent-shadow);
// z-index: 2;
}
.timeline-header__time {
// overflow: hidden;
position: relative;
flex-grow: 1;
height: 100%;
background-color: var(--body-color);
mask-image: linear-gradient(
90deg,
rgba(0, 0, 0, 0) 0,
rgba(0, 0, 0, 1) 2rem,
rgba(0, 0, 0, 1) calc(100% - 2rem),
rgba(0, 0, 0, 0) 100%
);
&-content {
width: 100%;
height: 100%;
will-change: transform;
.day,
.month {
position: absolute;
pointer-events: none;
}
.month {
width: max-content;
top: 0.25rem;
font-size: 1rem;
color: var(--accent-color);
&:first-letter {
text-transform: uppercase;
}
}
.day {
bottom: 0.5rem;
font-size: 1rem;
color: var(--content-color);
transform: translateX(-50%);
}
.cursor {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 1px;
width: 1.75rem;
height: 1.75rem;
bottom: 0.375rem;
font-size: 1rem;
font-weight: 600;
color: #fff;
background-color: var(--primary-bg-color);
border-radius: 50%;
transform: translateX(-50%);
pointer-events: none;
}
}
}
.todayMarker,
.monthMarker {
position: absolute;
top: 0;
bottom: 0;
width: 0;
height: 100%;
pointer-events: none;
}
.monthMarker {
border-left: 1px dashed var(--highlight-select);
}
.todayMarker {
border-left: 1px solid var(--primary-bg-color);
}
.timeline-background__headers,
.timeline-background__viewbox,
.timeline-foreground__viewbox {
overflow: hidden;
position: absolute;
top: 4rem;
bottom: 0;
height: 100%;
z-index: -1;
}
.timeline-background__headers {
left: 0;
background-color: var(--body-accent);
}
.timeline-background__viewbox,
.timeline-foreground__viewbox {
right: 0;
mask-image: linear-gradient(
90deg,
rgba(0, 0, 0, 0) 0,
rgba(0, 0, 0, 1) 2rem,
rgba(0, 0, 0, 1) calc(100% - 2rem),
rgba(0, 0, 0, 0) 100%
);
}
.timeline-foreground__viewbox {
z-index: 1;
pointer-events: none;
}
.timeline-splitter,
.timeline-splitter::before {
position: absolute;
top: 0;
bottom: 0;
height: 100%;
transform: translateX(-50%);
}
.timeline-splitter {
width: 1px;
background-color: var(--divider-color);
cursor: col-resize;
z-index: 3;
transition-property: width, background-color;
transition-timing-function: var(--timing-main);
transition-duration: 0.1s;
transition-delay: 0s;
&:hover {
width: 3px;
background-color: var(--button-border-hover);
transition-duration: 0.15s;
transition-delay: 0.3s;
}
&::before {
content: '';
width: 10px;
left: 50%;
}
&.moving {
width: 2px;
background-color: var(--primary-edit-border-color);
transition-duration: 0.1s;
transition-delay: 0s;
}
}
.headerWrapper {
display: flex;
align-items: center;
height: 100%;
min-width: 0;
padding-left: 0.75rem;
padding-right: 1.15rem;
// border-bottom: 1px solid var(--accent-bg-color);
}
.contentWrapper {
overflow: hidden;
position: relative;
display: flex;
align-items: center;
flex-grow: 1;
height: 100%;
min-width: 0;
min-height: 0;
mask-image: linear-gradient(
90deg,
rgba(0, 0, 0, 0) 0,
rgba(0, 0, 0, 1) 2rem,
rgba(0, 0, 0, 1) calc(100% - 2rem),
rgba(0, 0, 0, 0) 100%
);
&.nullRow {
cursor: pointer;
}
}
.timeline-wrapped_content {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
will-change: transform;
}
.timeline-action__button,
.project-item {
position: absolute;
display: flex;
align-items: center;
padding: 0.5rem;
box-shadow: var(--button-shadow);
}
.project-item {
top: 0.25rem;
bottom: 0.25rem;
background-color: var(--button-bg-color);
border: 1px solid var(--button-border-color);
border-radius: 0.75rem;
&:hover {
background-color: var(--button-bg-hover);
border-color: var(--button-border-hover);
}
&.noTarget {
mask-image: linear-gradient(to left, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 1) 2rem);
border-right-color: transparent;
}
.project-presenter {
display: flex;
align-items: center;
.space {
flex-shrink: 0;
width: 0.25rem;
min-width: 0.25rem;
max-width: 0.25rem;
}
}
}
.timeline-action__button {
top: 0.625rem;
bottom: 0.625rem;
width: 2rem;
color: var(--content-color);
background-color: var(--button-bg-color);
border: 1px solid var(--button-border-color);
border-radius: 0.5rem;
&:hover {
color: var(--accent-color);
background-color: var(--button-bg-hover);
border-color: var(--button-border-hover);
}
&.left {
left: 1rem;
}
&.right {
right: 1rem;
}
&.add {
transform: translateX(-50%);
pointer-events: none;
}
}
.listGrid {
display: flex;
justify-content: stretch;
align-items: center;
flex-shrink: 0;
width: 100%;
height: 3.25rem;
min-height: 0;
color: var(--caption-color);
z-index: 2;
&.mListGridChecked {
.headerWrapper {
background-color: var(--highlight-select);
}
.contentWrapper {
background-color: var(--trans-content-05);
}
.eListGridCheckBox {
opacity: 1;
}
}
&.mListGridSelected {
.headerWrapper {
background-color: var(--highlight-select-hover);
}
.contentWrapper {
background-color: var(--trans-content-10);
}
}
.eListGridCheckBox {
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
&:hover .eListGridCheckBox {
opacity: 1;
}
}
.filler {
display: flex;
flex-grow: 1;
}
.gridElement {
display: flex;
align-items: center;
justify-content: flex-start;
margin-left: 0.5rem;
&:first-child {
margin-left: 0;
}
}
.componentPresenter {
display: flex;
align-items: center;
flex-shrink: 0;
width: 5.5rem;
margin-left: 0.5rem;
}
</style>

View File

@ -52,11 +52,10 @@
if (issue.component) {
const component = await client.findOne(tracker.class.Component, { _id: issue.component })
projectLead = component?.lead || undefined
projectMembers = component?.members || []
} else {
projectLead = undefined
projectMembers = []
}
projectMembers = []
if (hasSpace(issue)) {
const project = await client.findOne(tracker.class.Project, { _id: issue.space })
if (project !== undefined) {

View File

@ -13,8 +13,6 @@
// limitations under the License.
-->
<script lang="ts">
import { EmployeeAccount } from '@hcengineering/contact'
import { getCurrentAccount } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Component, Issue, Project } from '@hcengineering/tracker'
import { Button, Label } from '@hcengineering/ui'
@ -41,12 +39,8 @@
async function createMissingComponent (cur: Component): Promise<void> {
await client.createDoc(cur._class, targetProject._id, {
label: cur.label,
members: [(getCurrentAccount() as EmployeeAccount).employee],
status: cur.status,
startDate: cur.startDate,
attachments: 0,
description: cur.description,
targetDate: cur.targetDate,
comments: 0,
lead: cur.lead
})

View File

@ -30,15 +30,11 @@ import { showPopup } from '@hcengineering/ui'
import ComponentEditor from './components/components/ComponentEditor.svelte'
import ComponentPresenter from './components/components/ComponentPresenter.svelte'
import Components from './components/components/Components.svelte'
import ComponentStatusEditor from './components/components/ComponentStatusEditor.svelte'
import ComponentStatusPresenter from './components/components/ComponentStatusPresenter.svelte'
import ComponentsTimeline from './components/components/ComponentsTimeline.svelte'
import ComponentTitlePresenter from './components/components/ComponentTitlePresenter.svelte'
import EditComponent from './components/components/EditComponent.svelte'
import IconPresenter from './components/components/IconComponent.svelte'
import LeadPresenter from './components/components/LeadPresenter.svelte'
import ProjectComponents from './components/components/ProjectComponents.svelte'
import TargetDatePresenter from './components/components/TargetDatePresenter.svelte'
import CreateIssue from './components/CreateIssue.svelte'
import Inbox from './components/inbox/Inbox.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
@ -388,7 +384,6 @@ export default async (): Promise<Resources> => ({
Inbox,
MyIssues,
Components,
ComponentsTimeline,
Views,
IssuePresenter,
ComponentPresenter,
@ -408,9 +403,6 @@ export default async (): Promise<Resources> => ({
NewIssueHeader,
IconPresenter,
LeadPresenter,
TargetDatePresenter,
ComponentStatusPresenter,
ComponentStatusEditor,
SetDueDateActionPopup,
SetParentIssueActionPopup,
EditComponent,

View File

@ -24,8 +24,7 @@ export default mergeIds(trackerId, tracker, {
viewlet: {
SubIssues: '' as Ref<Viewlet>,
List: '' as Ref<ViewletDescriptor>,
Kanban: '' as Ref<ViewletDescriptor>,
Timeline: '' as Ref<ViewletDescriptor>
Kanban: '' as Ref<ViewletDescriptor>
},
string: {
More: '' as IntlString,
@ -56,7 +55,6 @@ export default mergeIds(trackerId, tracker, {
CreateComponent: '' as IntlString,
ComponentNamePlaceholder: '' as IntlString,
ComponentDescriptionPlaceholder: '' as IntlString,
ComponentStatusPlaceholder: '' as IntlString,
ComponentLead: '' as IntlString,
ComponentMembers: '' as IntlString,
StartDate: '' as IntlString,
@ -312,7 +310,6 @@ export default mergeIds(trackerId, tracker, {
Views: '' as AnyComponent,
Issues: '' as AnyComponent,
Components: '' as AnyComponent,
ComponentsTimeline: '' as AnyComponent,
IssuePresenter: '' as AnyComponent,
ComponentTitlePresenter: '' as AnyComponent,
ComponentPresenter: '' as AnyComponent,
@ -337,9 +334,6 @@ export default mergeIds(trackerId, tracker, {
NewIssueHeader: '' as AnyComponent,
IconPresenter: '' as AnyComponent,
LeadPresenter: '' as AnyComponent,
TargetDatePresenter: '' as AnyComponent,
ComponentStatusPresenter: '' as AnyComponent,
ComponentStatusEditor: '' as AnyComponent,
SetDueDateActionPopup: '' as AnyComponent,
SetParentIssueActionPopup: '' as AnyComponent,
EditComponent: '' as AnyComponent,

View File

@ -21,7 +21,6 @@ import {
IssuesDateModificationPeriod,
IssuesGrouping,
IssuesOrdering,
ComponentStatus,
MilestoneStatus
} from '@hcengineering/tracker'
import tracker from './plugin'
@ -56,14 +55,6 @@ export const issuesDateModificationPeriodOptions: Record<IssuesDateModificationP
[IssuesDateModificationPeriod.PastWeek]: tracker.string.PastWeek,
[IssuesDateModificationPeriod.PastMonth]: tracker.string.PastMonth
}
export const defaultComponentStatuses = [
ComponentStatus.Backlog,
ComponentStatus.Planned,
ComponentStatus.InProgress,
ComponentStatus.Paused,
ComponentStatus.Completed,
ComponentStatus.Canceled
]
export const defaultMilestoneStatuses = [
MilestoneStatus.Planned,
@ -72,15 +63,6 @@ export const defaultMilestoneStatuses = [
MilestoneStatus.Canceled
]
export const componentStatusAssets: Record<ComponentStatus, { icon: Asset, label: IntlString }> = {
[ComponentStatus.Backlog]: { icon: tracker.icon.ComponentStatusBacklog, label: tracker.string.Backlog },
[ComponentStatus.Planned]: { icon: tracker.icon.ComponentStatusPlanned, label: tracker.string.Planned },
[ComponentStatus.InProgress]: { icon: tracker.icon.ComponentStatusInProgress, label: tracker.string.InProgress },
[ComponentStatus.Paused]: { icon: tracker.icon.ComponentStatusPaused, label: tracker.string.Paused },
[ComponentStatus.Completed]: { icon: tracker.icon.ComponentStatusCompleted, label: tracker.string.Completed },
[ComponentStatus.Canceled]: { icon: tracker.icon.ComponentStatusCanceled, label: tracker.string.Canceled }
}
export const milestoneStatusAssets: Record<MilestoneStatus, { icon: Asset, label: IntlString }> = {
[MilestoneStatus.Planned]: { icon: tracker.icon.MilestoneStatusPlanned, label: tracker.string.Planned },
[MilestoneStatus.InProgress]: { icon: tracker.icon.MilestoneStatusInProgress, label: tracker.string.InProgress },

View File

@ -39,7 +39,6 @@ import { Asset, IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { calcRank } from '@hcengineering/task'
import {
ComponentStatus,
Issue,
IssuePriority,
IssuesDateModificationPeriod,
@ -61,7 +60,7 @@ import {
import { ViewletDescriptor } from '@hcengineering/view'
import { CategoryQuery, groupBy, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
import tracker from './plugin'
import { defaultComponentStatuses, defaultPriorities, defaultMilestoneStatuses } from './types'
import { defaultPriorities, defaultMilestoneStatuses } from './types'
export * from './types'
@ -237,26 +236,6 @@ export type MilestoneViewMode = 'all' | 'planned' | 'active' | 'closed'
export type ScrumRecordViewMode = 'timeReports' | 'objects'
export const getIncludedComponentStatuses = (mode: ComponentsFilterMode): ComponentStatus[] => {
switch (mode) {
case 'all': {
return defaultComponentStatuses
}
case 'active': {
return [ComponentStatus.Planned, ComponentStatus.InProgress, ComponentStatus.Paused]
}
case 'backlog': {
return [ComponentStatus.Backlog]
}
case 'closed': {
return [ComponentStatus.Completed, ComponentStatus.Canceled]
}
default: {
return []
}
}
}
export const getIncludedMilestoneStatuses = (mode: MilestoneViewMode): MilestoneStatus[] => {
switch (mode) {
case 'all': {

View File

@ -297,39 +297,16 @@ export interface Document extends Doc {
space: Ref<Project>
}
/**
* @public
*/
export enum ComponentStatus {
Backlog,
Planned,
InProgress,
Paused,
Completed,
Canceled
}
/**
* @public
*/
export interface Component extends Doc {
label: string
description?: Markup
status: ComponentStatus
lead: Ref<Employee> | null
members: Ref<Employee>[]
space: Ref<Project>
comments: number
attachments?: number
startDate: Timestamp | null
targetDate: Timestamp | null
// Ref<Document>[]
}
/**
@ -378,7 +355,6 @@ export default plugin(trackerId, {
Component: '' as Ref<Class<Component>>,
IssueStatus: '' as Ref<Class<IssueStatus>>,
TypeIssuePriority: '' as Ref<Class<Type<IssuePriority>>>,
TypeComponentStatus: '' as Ref<Class<Type<ComponentStatus>>>,
Milestone: '' as Ref<Class<Milestone>>,
Scrum: '' as Ref<Class<Scrum>>,
ScrumRecord: '' as Ref<Class<ScrumRecord>>,
@ -448,14 +424,6 @@ export default plugin(trackerId, {
PriorityLow: '' as Asset,
ComponentsList: '' as Asset,
ComponentsTimeline: '' as Asset,
ComponentStatusBacklog: '' as Asset,
ComponentStatusPlanned: '' as Asset,
ComponentStatusInProgress: '' as Asset,
ComponentStatusPaused: '' as Asset,
ComponentStatusCompleted: '' as Asset,
ComponentStatusCanceled: '' as Asset,
MilestoneStatusPlanned: '' as Asset,
MilestoneStatusInProgress: '' as Asset,
@ -469,8 +437,6 @@ export default plugin(trackerId, {
TimeReport: '' as Asset,
Estimation: '' as Asset,
Timeline: '' as Asset,
// Project icons
Home: '' as Asset,
RedCircle: '' as Asset

View File

@ -21,42 +21,19 @@ test.describe('component tests', () => {
)
await page.click('button:has-text("Component")')
await page.click('[placeholder="Component\\ name"]')
const prjId = 'component-' + generateId()
await page.fill('[placeholder="Component\\ name"]', prjId)
const componentName = 'component-' + generateId()
await page.fill('[placeholder="Component\\ name"]', componentName)
await page.click('button:has-text("Create component")')
await fillSearch(page, prjId)
await fillSearch(page, componentName)
await page.click(`text=${prjId}`)
await page.click(`text=${componentName}`)
await page.click('button:has-text("New issue")')
await page.fill('[placeholder="Issue\\ title"]', 'issue')
await page.click('form button:has-text("Component")')
await page.click(`.selectPopup button:has-text("${prjId}")`)
await page.click(`.selectPopup button:has-text("${componentName}")`)
await page.click('form button:has-text("Create issue")')
await page.waitForSelector('form.antiCard', { state: 'detached' })
await page.click('.listGrid :has-text("issue")')
})
test('create-component-with-status', async ({ page }) => {
await page.click('[id="app-tracker\\:string\\:TrackerApplication"]')
await page.click('text=Components')
await expect(page).toHaveURL(
`${PlatformURI}/workbench/sanity-ws/tracker/tracker%3Aproject%3ADefaultProject/components`
)
await page.click('button:has-text("Component")')
const prjId = 'component-' + generateId()
await page.fill('[placeholder="Component\\ name"]', prjId)
await page.click('text=Backlog Lead Members Start date Target date >> button')
await page.click('button:has-text("In progress")')
await page.click('button:has-text("Create component")')
await page.waitForSelector('form.antiCard', { state: 'detached' })
await fillSearch(page, prjId)
await page.click(`text=${prjId}`)
await page.click('button:has-text("In progress")')
await page.click('button:has-text("Completed")')
})
})