mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 08:57:14 +03:00
[UBER-82] Update components (#3227)
Signed-off-by: Ruslan Bayandinov <wazsone@ya.ru>
This commit is contained in:
parent
fb5c4b1f41
commit
2f4cb2c2a7
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -29,7 +29,6 @@
|
||||
"CreateComponent": "Create component",
|
||||
"ComponentNamePlaceholder": "Component name",
|
||||
"ComponentDescriptionPlaceholder": "Description (optional)",
|
||||
"ComponentStatusPlaceholder": "Change component status...",
|
||||
"ComponentLead": "Lead",
|
||||
"ComponentMembers": "Members",
|
||||
"StartDate": "Start date",
|
||||
|
@ -29,7 +29,6 @@
|
||||
"CreateComponent": "Создать компонент",
|
||||
"ComponentNamePlaceholder": "Название компонента",
|
||||
"ComponentDescriptionPlaceholder": "Описание (необязательно)",
|
||||
"ComponentStatusPlaceholder": "Сменить статус компонента...",
|
||||
"ComponentLead": "Руководитель",
|
||||
"ComponentMembers": "Участники",
|
||||
"StartDate": "Дата начала",
|
||||
|
@ -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`))
|
||||
|
@ -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}>
|
||||
|
@ -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}
|
||||
|
@ -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 />
|
@ -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}
|
@ -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}
|
@ -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} />
|
||||
|
@ -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,
|
||||
|
@ -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)}
|
||||
/>
|
@ -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)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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} />
|
@ -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>
|
@ -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) {
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 },
|
||||
|
@ -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': {
|
||||
|
@ -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
|
||||
|
@ -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")')
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user