mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 20:45:00 +02:00
Sprints List and Reports view (#2602)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -66,7 +66,7 @@
|
||||
<TableBrowser
|
||||
showFilterBar={false}
|
||||
_class={tracker.class.TimeSpendReport}
|
||||
query={{ attachedTo: { $in: [issue._id, ...issue.childInfo.map((it) => it.childId)] } }}
|
||||
query={{ attachedTo: { $in: [issue._id, ...(issue.childInfo?.map((it) => it.childId) ?? [])] } }}
|
||||
config={[
|
||||
'$lookup.attachedTo',
|
||||
'',
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
export let value: Employee | null
|
||||
export let _class: Ref<Class<Project | Sprint>>
|
||||
export let size: IconSize = 'x-small'
|
||||
export let parentId: Ref<Project>
|
||||
export let parentId: Ref<Doc>
|
||||
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let isEditable: boolean = true
|
||||
export let shouldShowLabel: boolean = false
|
||||
@@ -54,7 +54,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
const currentParent = await client.findOne(_class, { _id: parentId })
|
||||
const currentParent = await client.findOne(_class, { _id: parentId as Ref<Project> })
|
||||
|
||||
if (currentParent === undefined) {
|
||||
return
|
||||
|
||||
@@ -13,62 +13,84 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import { DocumentQuery, FindOptions, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||
import { DocumentQuery, WithLookup } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { Button, defaultSP, Icon, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
|
||||
import { Button, IconAdd, Label, SearchEdit, showPopup } from '@hcengineering/ui'
|
||||
import view, { Viewlet } from '@hcengineering/view'
|
||||
import {
|
||||
FilterBar,
|
||||
FilterButton,
|
||||
getActiveViewletId,
|
||||
getViewOptions,
|
||||
setActiveViewletId,
|
||||
ViewletSettingButton
|
||||
} from '@hcengineering/view-resources'
|
||||
import tracker from '../../plugin'
|
||||
import { getIncludedSprintStatuses, sprintTitleMap, SprintViewMode } from '../../utils'
|
||||
import NewSprint from './NewSprint.svelte'
|
||||
import SprintDatePresenter from './SprintDatePresenter.svelte'
|
||||
import SprintListBrowser from './SprintListBrowser.svelte'
|
||||
import SprintProjectEditor from './SprintProjectEditor.svelte'
|
||||
import SprintContent from './SprintContent.svelte'
|
||||
|
||||
export let label: IntlString
|
||||
export let query: DocumentQuery<Sprint> = {}
|
||||
export let search: string = ''
|
||||
export let mode: SprintViewMode = 'all'
|
||||
|
||||
const ENTRIES_LIMIT = 200
|
||||
const resultSprintsQuery = createQuery()
|
||||
|
||||
const sprintOptions: FindOptions<Sprint> = {
|
||||
sort: { startDate: SortingOrder.Descending },
|
||||
limit: ENTRIES_LIMIT,
|
||||
lookup: {
|
||||
lead: contact.class.Employee,
|
||||
project: tracker.class.Project
|
||||
}
|
||||
const space = typeof query.space === 'string' ? query.space : tracker.team.DefaultTeam
|
||||
const showCreateDialog = async () => {
|
||||
showPopup(NewSprint, { space, targetElement: null }, 'top')
|
||||
}
|
||||
|
||||
let resultSprints: WithLookup<Sprint>[] = []
|
||||
export let panelWidth: number = 0
|
||||
|
||||
let viewlet: WithLookup<Viewlet> | undefined = undefined
|
||||
|
||||
let searchQuery: DocumentQuery<Sprint> = { ...query }
|
||||
function updateSearchQuery (search: string): void {
|
||||
searchQuery = search === '' ? { ...query } : { ...query, $search: search }
|
||||
}
|
||||
$: if (query) updateSearchQuery(search)
|
||||
|
||||
$: includedSprintStatuses = getIncludedSprintStatuses(mode)
|
||||
$: title = sprintTitleMap[mode]
|
||||
$: includedSprintsQuery = { status: { $in: includedSprintStatuses } }
|
||||
|
||||
$: baseQuery = {
|
||||
...includedSprintsQuery,
|
||||
...query
|
||||
const client = getClient()
|
||||
let resultQuery: DocumentQuery<Sprint> = { ...searchQuery }
|
||||
|
||||
let viewlets: WithLookup<Viewlet>[] = []
|
||||
|
||||
$: update()
|
||||
|
||||
async function update (): Promise<void> {
|
||||
viewlets = await client.findAll(
|
||||
view.class.Viewlet,
|
||||
{ attachTo: tracker.class.Sprint },
|
||||
{
|
||||
lookup: {
|
||||
descriptor: view.class.ViewletDescriptor
|
||||
}
|
||||
}
|
||||
)
|
||||
const _id = getActiveViewletId()
|
||||
viewlet = viewlets.find((viewlet) => viewlet._id === _id) || viewlets[0]
|
||||
setActiveViewletId(viewlet._id)
|
||||
}
|
||||
|
||||
$: resultQuery = search === '' ? baseQuery : { $search: search, ...baseQuery }
|
||||
|
||||
$: resultSprintsQuery.query<Sprint>(
|
||||
tracker.class.Sprint,
|
||||
{ ...resultQuery },
|
||||
(result) => {
|
||||
resultSprints = result
|
||||
},
|
||||
sprintOptions
|
||||
)
|
||||
|
||||
const space = typeof query.space === 'string' ? query.space : tracker.team.DefaultTeam
|
||||
const showCreateDialog = async () => {
|
||||
showPopup(NewSprint, { space, targetElement: null }, 'top')
|
||||
let asideFloat: boolean = false
|
||||
let asideShown: boolean = true
|
||||
$: if (panelWidth < 900 && !asideFloat) asideFloat = true
|
||||
$: if (panelWidth >= 900 && asideFloat) {
|
||||
asideFloat = false
|
||||
asideShown = false
|
||||
}
|
||||
let docWidth: number
|
||||
let docSize: boolean = false
|
||||
$: if (docWidth <= 900 && !docSize) docSize = true
|
||||
$: if (docWidth > 900 && docSize) docSize = false
|
||||
|
||||
$: viewOptions = getViewOptions(viewlet)
|
||||
|
||||
const handleViewModeChanged = (newMode: SprintViewMode) => {
|
||||
if (newMode === undefined || newMode === mode) {
|
||||
@@ -77,18 +99,25 @@
|
||||
|
||||
mode = newMode
|
||||
}
|
||||
|
||||
const retrieveMembers = (s: Sprint) => s.members
|
||||
</script>
|
||||
|
||||
<div class="fs-title flex-between header">
|
||||
<div class="flex-center">
|
||||
<div class="flex-row-center">
|
||||
<Label {label} />
|
||||
<div class="projectTitle">
|
||||
› <Label label={title} />
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<FilterButton _class={tracker.class.Issue} {space} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-center gap-2">
|
||||
<SearchEdit bind:value={search} on:change={() => {}} />
|
||||
<Button size="small" icon={IconAdd} label={tracker.string.Sprint} kind={'primary'} on:click={showCreateDialog} />
|
||||
{#if viewlet}
|
||||
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||
{/if}
|
||||
</div>
|
||||
<Button size="small" icon={IconAdd} label={tracker.string.Sprint} kind={'primary'} on:click={showCreateDialog} />
|
||||
</div>
|
||||
<div class="itemsContainer">
|
||||
<div class="flex-center">
|
||||
@@ -130,66 +159,23 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="ml-3 filterButton">
|
||||
<Button
|
||||
size="small"
|
||||
icon={IconAdd}
|
||||
kind={'link-bordered'}
|
||||
borderStyle={'dashed'}
|
||||
label={tracker.string.Filter}
|
||||
on:click={() => {}}
|
||||
/>
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <div class="flex-center">
|
||||
<div class="flex-center">
|
||||
<div class="buttonWrapper">
|
||||
<Button selected size="small" shape="rectangle-right" icon={tracker.icon.ProjectsList} />
|
||||
</div>
|
||||
<div class="buttonWrapper">
|
||||
<Button size="small" shape="rectangle-left" icon={tracker.icon.ProjectsTimeline} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<Button size="small" icon={IconOptions} />
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="w-full h-full clear-mins">
|
||||
<Scroller fade={defaultSP}>
|
||||
<SprintListBrowser
|
||||
_class={tracker.class.Sprint}
|
||||
itemsConfig={[
|
||||
{ key: '', presenter: Icon, props: { icon: tracker.icon.Sprint, size: 'small' } },
|
||||
{ key: '', presenter: tracker.component.SprintPresenter, props: { kind: 'list' } },
|
||||
{ key: '', presenter: SprintProjectEditor, props: { kind: 'list' } },
|
||||
{
|
||||
key: '$lookup.lead',
|
||||
presenter: tracker.component.LeadPresenter,
|
||||
props: {
|
||||
_class: tracker.class.Sprint,
|
||||
defaultClass: contact.class.Employee,
|
||||
shouldShowLabel: false,
|
||||
size: 'x-small'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
presenter: contact.component.MembersPresenter,
|
||||
props: {
|
||||
kind: 'link',
|
||||
intlTitle: tracker.string.SprintMembersTitle,
|
||||
intlSearchPh: tracker.string.SprintMembersSearchPlaceholder,
|
||||
retrieveMembers
|
||||
}
|
||||
},
|
||||
{ key: '', presenter: SprintDatePresenter, props: { field: 'startDate' } },
|
||||
{ key: '', presenter: SprintDatePresenter, props: { field: 'targetDate' } },
|
||||
{ key: '', presenter: tracker.component.SprintStatusPresenter }
|
||||
]}
|
||||
sprints={resultSprints}
|
||||
/>
|
||||
</Scroller>
|
||||
<FilterBar
|
||||
_class={tracker.class.Sprint}
|
||||
query={searchQuery}
|
||||
{viewOptions}
|
||||
on:change={(e) => (resultQuery = e.detail)}
|
||||
/>
|
||||
<div class="flex w-full h-full clear-mins">
|
||||
{#if viewlet}
|
||||
<SprintContent {viewlet} query={{ ...resultQuery, ...includedSprintsQuery }} {space} {viewOptions} />
|
||||
{/if}
|
||||
{#if $$slots.aside !== undefined && asideShown}
|
||||
<div class="popupPanel-body__aside flex" class:float={asideFloat} class:shown={asideShown}>
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -220,8 +206,4 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// .filterButton {
|
||||
// color: var(--caption-color);
|
||||
// }
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import { BuildModelKey, Viewlet, ViewOptions } from '@hcengineering/view'
|
||||
import tracker from '../../plugin'
|
||||
import NewSprint from './NewSprint.svelte'
|
||||
|
||||
export let viewlet: WithLookup<Viewlet>
|
||||
export let query: DocumentQuery<Sprint> = {}
|
||||
export let space: Ref<Space> | undefined
|
||||
|
||||
// Extra properties
|
||||
export let viewOptions: ViewOptions
|
||||
|
||||
const createItemDialog = NewSprint
|
||||
const createItemLabel = tracker.string.CreateSprint
|
||||
|
||||
const retrieveMembers = (s: Sprint) => 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}
|
||||
<Component
|
||||
is={viewlet.$lookup.descriptor.component}
|
||||
props={{
|
||||
_class: tracker.class.Sprint,
|
||||
config: updateConfig(viewlet.config),
|
||||
options: viewlet.options,
|
||||
createItemDialog,
|
||||
createItemLabel,
|
||||
viewlet,
|
||||
viewOptions,
|
||||
viewOptionsConfig: viewlet.viewOptions?.other,
|
||||
space,
|
||||
query,
|
||||
props: {}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@@ -0,0 +1,105 @@
|
||||
<!--
|
||||
// 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 contact, { Employee } from '@hcengineering/contact'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { getClient, UsersPopup } from '@hcengineering/presentation'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { eventToHTMLElement, IconSize, showPopup } from '@hcengineering/ui'
|
||||
import { AttributeModel } from '@hcengineering/view'
|
||||
import { getObjectPresenter } from '@hcengineering/view-resources'
|
||||
import tracker from '../../plugin'
|
||||
import LeadPopup from '../projects/LeadPopup.svelte'
|
||||
|
||||
export let value: Employee | null
|
||||
export let size: IconSize = 'x-small'
|
||||
export let object: Sprint
|
||||
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let isEditable: boolean = true
|
||||
export let shouldShowLabel: boolean = false
|
||||
export let defaultName: IntlString | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let presenter: AttributeModel | undefined
|
||||
|
||||
$: if (value || defaultClass) {
|
||||
if (value) {
|
||||
getObjectPresenter(client, value._class, { key: '' }).then((p) => {
|
||||
presenter = p
|
||||
})
|
||||
} else if (defaultClass) {
|
||||
getObjectPresenter(client, defaultClass, { key: '' }).then((p) => {
|
||||
presenter = p
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleLeadChanged = async (result: Employee | null | undefined) => {
|
||||
if (!isEditable || result === undefined) {
|
||||
return
|
||||
}
|
||||
const newLead = result === null ? null : result._id
|
||||
|
||||
await client.update(object, { lead: newLead })
|
||||
}
|
||||
|
||||
const handleLeadEditorOpened = async (event: MouseEvent) => {
|
||||
if (!isEditable) {
|
||||
return
|
||||
}
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{
|
||||
_class: contact.class.Employee,
|
||||
selected: value?._id,
|
||||
docQuery: {
|
||||
active: true
|
||||
},
|
||||
allowDeselect: true,
|
||||
placeholder: tracker.string.ProjectLeadSearchPlaceholder
|
||||
},
|
||||
eventToHTMLElement(event),
|
||||
handleLeadChanged
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value && presenter}
|
||||
<svelte:component
|
||||
this={presenter.presenter}
|
||||
{value}
|
||||
{defaultName}
|
||||
avatarSize={size}
|
||||
isInteractive={true}
|
||||
shouldShowPlaceholder={true}
|
||||
shouldShowName={shouldShowLabel}
|
||||
onEmployeeEdit={handleLeadEditorOpened}
|
||||
tooltipLabels={{ component: LeadPopup, props: { lead: value } }}
|
||||
/>
|
||||
{:else if presenter}
|
||||
<svelte:component
|
||||
this={presenter.presenter}
|
||||
{value}
|
||||
{defaultName}
|
||||
avatarSize={size}
|
||||
isInteractive={true}
|
||||
shouldShowPlaceholder={true}
|
||||
shouldShowName={shouldShowLabel}
|
||||
onEmployeeEdit={handleLeadEditorOpened}
|
||||
tooltipLabels={{ personLabel: tracker.string.AssignedTo, placeholderLabel: tracker.string.AssignTo }}
|
||||
/>
|
||||
{/if}
|
||||
@@ -1,292 +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 contact from '@hcengineering/contact'
|
||||
import { Class, Doc, FindOptions, getObjectValue, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Issue, Project, Sprint } from '@hcengineering/tracker'
|
||||
import { CheckBox, ExpandCollapse, Spinner, tooltip } from '@hcengineering/ui'
|
||||
import { BuildModelKey } from '@hcengineering/view'
|
||||
import { buildModel, LoadingProps } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import SprintProjectEditor from './SprintProjectEditor.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let itemsConfig: (BuildModelKey | string)[]
|
||||
export let selectedObjectIds: Doc[] = []
|
||||
export let selectedRowIndex: number | undefined = undefined
|
||||
export let sprints: WithLookup<Sprint>[] | undefined = undefined
|
||||
export let loadingProps: LoadingProps | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const client = getClient()
|
||||
const objectRefs: HTMLElement[] = []
|
||||
|
||||
const baseOptions: FindOptions<Issue> = {
|
||||
lookup: {
|
||||
assignee: contact.class.Employee,
|
||||
status: tracker.class.IssueStatus
|
||||
}
|
||||
}
|
||||
|
||||
$: options = { ...baseOptions } as FindOptions<Sprint>
|
||||
$: selectedObjectIdsSet = new Set<Ref<Doc>>(selectedObjectIds.map((it) => it._id))
|
||||
$: objectRefs.length = sprints?.length ?? 0
|
||||
|
||||
$: byProject = sprints?.reduce((s, cur) => {
|
||||
const pid = cur.project ?? ''
|
||||
s.set(pid, [...(s.get(pid) ?? []), cur])
|
||||
return s
|
||||
}, new Map<Ref<Project> | '', WithLookup<Sprint>[]>())
|
||||
|
||||
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 (!sprints) {
|
||||
return
|
||||
}
|
||||
|
||||
let position =
|
||||
(docObject !== undefined ? sprints?.findIndex((x) => x._id === docObject?._id) : selectedRowIndex) ?? -1
|
||||
|
||||
position += offset
|
||||
|
||||
if (position < 0) {
|
||||
position = 0
|
||||
}
|
||||
|
||||
if (position >= sprints.length) {
|
||||
position = sprints.length - 1
|
||||
}
|
||||
|
||||
const objectRef = objectRefs[position]
|
||||
|
||||
selectedRowIndex = position
|
||||
|
||||
handleRowFocused(sprints[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
|
||||
}
|
||||
|
||||
const isCollapsedMap: Record<any, boolean> = {}
|
||||
|
||||
$: {
|
||||
const exkeys = new Set(Object.keys(isCollapsedMap))
|
||||
for (const c of byProject?.keys() ?? []) {
|
||||
if (!exkeys.delete(c)) {
|
||||
isCollapsedMap[c] = false
|
||||
}
|
||||
}
|
||||
for (const k of exkeys) {
|
||||
delete isCollapsedMap[k]
|
||||
}
|
||||
}
|
||||
|
||||
const handleCollapseCategory = (category: any) => (isCollapsedMap[category] = !isCollapsedMap[category])
|
||||
</script>
|
||||
|
||||
{#await buildModel({ client, _class, keys: itemsConfig, lookup: options.lookup }) then itemModels}
|
||||
<div class="listRoot">
|
||||
{#if sprints}
|
||||
{#each Array.from(byProject?.entries() ?? []) as e}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="flex-between categoryHeader row" on:click={() => handleCollapseCategory(e[0])}>
|
||||
<div class="flex-row-center gap-2 clear-mins">
|
||||
<SprintProjectEditor
|
||||
isEditable={false}
|
||||
value={e[1][0]}
|
||||
enlargedText={true}
|
||||
kind={'list-header'}
|
||||
shouldShowPlaceholder={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ExpandCollapse isExpanded={!isCollapsedMap[e[0]]} duration={400}>
|
||||
{#each e[1] as docObject (docObject._id)}
|
||||
<div
|
||||
bind:this={objectRefs[sprints.findIndex((x) => x === docObject)]}
|
||||
class="listGrid"
|
||||
class:mListGridChecked={selectedObjectIdsSet.has(docObject._id)}
|
||||
class:mListGridFixed={selectedRowIndex === sprints.findIndex((x) => x === docObject)}
|
||||
class:mListGridSelected={selectedRowIndex === sprints.findIndex((x) => x === docObject)}
|
||||
on:focus={() => {}}
|
||||
on:mouseover={() => handleRowFocused(docObject)}
|
||||
>
|
||||
<div class="contentWrapper">
|
||||
{#each itemModels as attributeModel, attributeModelIndex}
|
||||
{#if attributeModelIndex === 0}
|
||||
<div class="gridElement">
|
||||
<div
|
||||
class="eListGridCheckBox"
|
||||
use:tooltip={{ direction: 'bottom', label: tracker.string.SelectIssue }}
|
||||
>
|
||||
<CheckBox
|
||||
checked={selectedObjectIdsSet.has(docObject._id)}
|
||||
on:value={(event) => {
|
||||
onObjectChecked([docObject], event.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="iconPresenter">
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
{...attributeModel.props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if attributeModelIndex === 1}
|
||||
<div class="projectPresenter flex-grow">
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
{...attributeModel.props}
|
||||
/>
|
||||
</div>
|
||||
<div class="filler" />
|
||||
{:else}
|
||||
<div class="gridElement">
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
parentId={docObject._id}
|
||||
sprintId={docObject._id}
|
||||
{...attributeModel.props}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</ExpandCollapse>
|
||||
{/each}
|
||||
{: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}
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
<style lang="scss">
|
||||
.listRoot {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.categoryHeader {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 0 1.5rem 0 2.25rem;
|
||||
height: 3rem;
|
||||
min-height: 3rem;
|
||||
min-width: 0;
|
||||
background-color: var(--accent-bg-color);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 1.15rem;
|
||||
}
|
||||
|
||||
.listGrid {
|
||||
width: 100%;
|
||||
height: 3.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
border-bottom: 1px solid var(--theme-button-border-hovered);
|
||||
|
||||
&.mListGridChecked {
|
||||
background-color: var(--theme-table-bg-hover);
|
||||
|
||||
.eListGridCheckBox {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.mListGridSelected {
|
||||
background-color: var(--menu-bg-select);
|
||||
}
|
||||
|
||||
.eListGridCheckBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.iconPresenter {
|
||||
padding-left: 0.45rem;
|
||||
}
|
||||
|
||||
.projectPresenter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 5.5rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,72 +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 type { Class, Doc, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { BuildModelKey } from '@hcengineering/view'
|
||||
import {
|
||||
ActionContext,
|
||||
focusStore,
|
||||
ListSelectionProvider,
|
||||
LoadingProps,
|
||||
SelectDirection,
|
||||
selectionStore
|
||||
} from '@hcengineering/view-resources'
|
||||
import { onMount } from 'svelte'
|
||||
import SprintList from './SprintList.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let itemsConfig: (BuildModelKey | string)[]
|
||||
export let loadingProps: LoadingProps | undefined = undefined
|
||||
export let sprints: WithLookup<Sprint>[] = []
|
||||
|
||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||
if (dir === 'vertical') {
|
||||
sprintsList.onElementSelected(offset, of)
|
||||
}
|
||||
})
|
||||
|
||||
let sprintsList: SprintList
|
||||
|
||||
$: if (sprintsList !== undefined) {
|
||||
listProvider.update(sprints)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
;(document.activeElement as HTMLElement)?.blur()
|
||||
})
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
context={{
|
||||
mode: 'browser'
|
||||
}}
|
||||
/>
|
||||
|
||||
<SprintList
|
||||
bind:this={sprintsList}
|
||||
{_class}
|
||||
{itemsConfig}
|
||||
{loadingProps}
|
||||
{sprints}
|
||||
selectedObjectIds={$selectionStore ?? []}
|
||||
selectedRowIndex={listProvider.current($focusStore)}
|
||||
on:row-focus={(event) => {
|
||||
listProvider.updateFocus(event.detail ?? undefined)
|
||||
}}
|
||||
on:check={(event) => {
|
||||
listProvider.updateSelection(event.detail.docs, event.detail.value)
|
||||
}}
|
||||
/>
|
||||
Reference in New Issue
Block a user