mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-26 11:34:58 +02:00
Allow to specify project to sprints (#2275)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
import login from '@hcengineering/login'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import { Avatar, createQuery } from '@hcengineering/presentation'
|
||||
import setting, { SettingsCategory, settingId } from '@hcengineering/setting'
|
||||
import setting, { settingId, SettingsCategory } from '@hcengineering/setting'
|
||||
import {
|
||||
closePopup,
|
||||
fetchMetadataLocalStorage,
|
||||
|
||||
@@ -380,8 +380,12 @@
|
||||
}}
|
||||
/>
|
||||
<EstimationEditor kind={'no-border'} size={'small'} value={object} />
|
||||
<ProjectSelector value={object.project} onProjectIdChange={handleProjectIdChanged} />
|
||||
<SprintSelector value={object.sprint} onSprintIdChange={handleSprintIdChanged} />
|
||||
<ProjectSelector value={object.project} onChange={handleProjectIdChanged} />
|
||||
<SprintSelector
|
||||
value={object.sprint}
|
||||
onChange={handleSprintIdChanged}
|
||||
useProject={object.project ?? undefined}
|
||||
/>
|
||||
{#if object.dueDate !== null}
|
||||
<DatePresenter bind:value={object.dueDate} editable />
|
||||
{/if}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
export let value: Ref<Project> | null | undefined
|
||||
export let shouldShowLabel: boolean = true
|
||||
export let isEditable: boolean = true
|
||||
export let onProjectIdChange: ((newProjectId: Ref<Project> | undefined) => void) | undefined = undefined
|
||||
export let onChange: ((newProjectId: Ref<Project> | undefined) => void) | undefined = undefined
|
||||
export let popupPlaceholder: IntlString = tracker.string.AddToProject
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'small'
|
||||
@@ -87,7 +87,7 @@
|
||||
SelectPopup,
|
||||
{ value: projectsInfo, placeholder: popupPlaceholder, searchable: true },
|
||||
eventToHTMLElement(event),
|
||||
onProjectIdChange
|
||||
onChange
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,17 +15,19 @@
|
||||
<script lang="ts">
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Project } from '@hcengineering/tracker'
|
||||
import { Project, Sprint } from '@hcengineering/tracker'
|
||||
import { UsersPopup, getClient } from '@hcengineering/presentation'
|
||||
import { AttributeModel } from '@hcengineering/view'
|
||||
import { eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { eventToHTMLElement, IconSize, showPopup } from '@hcengineering/ui'
|
||||
import { getObjectPresenter } from '@hcengineering/view-resources'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import tracker from '../../plugin'
|
||||
import LeadPopup from './LeadPopup.svelte'
|
||||
|
||||
export let value: Employee | null
|
||||
export let projectId: Ref<Project>
|
||||
export let _class: Ref<Class<Project | Sprint>>
|
||||
export let size: IconSize = 'x-small'
|
||||
export let parentId: Ref<Project>
|
||||
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let isEditable: boolean = true
|
||||
export let shouldShowLabel: boolean = false
|
||||
@@ -52,15 +54,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
const currentProject = await client.findOne(tracker.class.Project, { _id: projectId })
|
||||
const currentParent = await client.findOne(_class, { _id: parentId })
|
||||
|
||||
if (currentProject === undefined) {
|
||||
if (currentParent === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const newLead = result === null ? null : result._id
|
||||
|
||||
await client.update(currentProject, { lead: newLead })
|
||||
await client.update(currentParent, { lead: newLead })
|
||||
}
|
||||
|
||||
const handleLeadEditorOpened = async (event: MouseEvent) => {
|
||||
@@ -89,7 +91,7 @@
|
||||
this={presenter.presenter}
|
||||
{value}
|
||||
{defaultName}
|
||||
avatarSize={'tiny'}
|
||||
avatarSize={size}
|
||||
isInteractive={true}
|
||||
shouldShowPlaceholder={true}
|
||||
shouldShowName={shouldShowLabel}
|
||||
@@ -101,7 +103,7 @@
|
||||
this={presenter.presenter}
|
||||
{value}
|
||||
{defaultName}
|
||||
avatarSize={'tiny'}
|
||||
avatarSize={size}
|
||||
isInteractive={true}
|
||||
shouldShowPlaceholder={true}
|
||||
shouldShowName={shouldShowLabel}
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
{
|
||||
key: '$lookup.lead',
|
||||
presenter: tracker.component.LeadPresenter,
|
||||
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||
props: { _class: tracker.class.Project, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||
},
|
||||
{ key: '', presenter: tracker.component.ProjectMembersPresenter, props: { kind: 'link' } },
|
||||
{ key: '', presenter: tracker.component.TargetDatePresenter },
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
{onlyIcon}
|
||||
{enlargedText}
|
||||
value={value.project}
|
||||
onProjectIdChange={handleProjectIdChanged}
|
||||
onChange={handleProjectIdChanged}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -14,16 +14,17 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import { DocumentQuery, FindOptions, SortingOrder } from '@hcengineering/core'
|
||||
import { DocumentQuery, FindOptions, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { Button, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import { Button, defaultSP, Icon, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
|
||||
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'
|
||||
|
||||
export let label: IntlString
|
||||
export let query: DocumentQuery<Sprint> = {}
|
||||
@@ -36,10 +37,13 @@
|
||||
const sprintOptions: FindOptions<Sprint> = {
|
||||
sort: { startDate: SortingOrder.Descending },
|
||||
limit: ENTRIES_LIMIT,
|
||||
lookup: { lead: contact.class.Employee }
|
||||
lookup: {
|
||||
lead: contact.class.Employee,
|
||||
project: tracker.class.Project
|
||||
}
|
||||
}
|
||||
|
||||
let resultSprints: Sprint[] = []
|
||||
let resultSprints: WithLookup<Sprint>[] = []
|
||||
|
||||
$: includedSprintStatuses = getIncludedSprintStatuses(mode)
|
||||
$: title = sprintTitleMap[mode]
|
||||
@@ -75,57 +79,56 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="fs-title flex-between header">
|
||||
<div class="fs-title flex-between header">
|
||||
<div class="flex-center">
|
||||
<Label {label} />
|
||||
<div class="projectTitle">
|
||||
› <Label label={title} />
|
||||
</div>
|
||||
</div>
|
||||
<Button size="small" icon={IconAdd} label={tracker.string.Sprint} kind="secondary" on:click={showCreateDialog} />
|
||||
</div>
|
||||
<div class="itemsContainer">
|
||||
<div class="flex-center">
|
||||
<div class="flex-center">
|
||||
<Label {label} />
|
||||
<div class="projectTitle">
|
||||
› <Label label={title} />
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle-right"
|
||||
selected={mode === 'all'}
|
||||
label={tracker.string.AllSprints}
|
||||
on:click={() => handleViewModeChanged('all')}
|
||||
/>
|
||||
</div>
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle"
|
||||
selected={mode === 'planned'}
|
||||
label={tracker.string.PlannedSprints}
|
||||
on:click={() => handleViewModeChanged('planned')}
|
||||
/>
|
||||
</div>
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle"
|
||||
selected={mode === 'active'}
|
||||
label={tracker.string.ActiveSprints}
|
||||
on:click={() => handleViewModeChanged('active')}
|
||||
/>
|
||||
</div>
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle-left"
|
||||
selected={mode === 'closed'}
|
||||
label={tracker.string.ClosedSprints}
|
||||
on:click={() => handleViewModeChanged('closed')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="small" icon={IconAdd} label={tracker.string.Sprint} kind="secondary" on:click={showCreateDialog} />
|
||||
</div>
|
||||
<div class="itemsContainer">
|
||||
<div class="flex-center">
|
||||
<div class="flex-center">
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle-right"
|
||||
selected={mode === 'all'}
|
||||
label={tracker.string.AllSprints}
|
||||
on:click={() => handleViewModeChanged('all')}
|
||||
/>
|
||||
</div>
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle"
|
||||
selected={mode === 'planned'}
|
||||
label={tracker.string.PlannedSprints}
|
||||
on:click={() => handleViewModeChanged('planned')}
|
||||
/>
|
||||
</div>
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle"
|
||||
selected={mode === 'active'}
|
||||
label={tracker.string.ActiveSprints}
|
||||
on:click={() => handleViewModeChanged('active')}
|
||||
/>
|
||||
</div>
|
||||
<div class="buttonWrapper">
|
||||
<Button
|
||||
size="small"
|
||||
shape="rectangle-left"
|
||||
selected={mode === 'closed'}
|
||||
label={tracker.string.ClosedSprints}
|
||||
on:click={() => handleViewModeChanged('closed')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="ml-3 filterButton">
|
||||
<!-- <div class="ml-3 filterButton">
|
||||
<Button
|
||||
size="small"
|
||||
icon={IconAdd}
|
||||
@@ -135,8 +138,8 @@
|
||||
on:click={() => {}}
|
||||
/>
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <div class="flex-center">
|
||||
</div>
|
||||
<!-- <div class="flex-center">
|
||||
<div class="flex-center">
|
||||
<div class="buttonWrapper">
|
||||
<Button selected size="small" shape="rectangle-right" icon={tracker.icon.ProjectsList} />
|
||||
@@ -149,23 +152,32 @@
|
||||
<Button size="small" icon={IconOptions} />
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<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: '$lookup.lead',
|
||||
presenter: tracker.component.LeadPresenter,
|
||||
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||
},
|
||||
{ key: '', presenter: SprintDatePresenter, props: { field: 'startDate' } },
|
||||
{ key: '', presenter: SprintDatePresenter, props: { field: 'targetDate' } },
|
||||
{ key: '', presenter: tracker.component.SprintStatusPresenter }
|
||||
]}
|
||||
sprints={resultSprints}
|
||||
/>
|
||||
</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: SprintDatePresenter, props: { field: 'startDate' } },
|
||||
{ key: '', presenter: SprintDatePresenter, props: { field: 'targetDate' } },
|
||||
{ key: '', presenter: tracker.component.SprintStatusPresenter }
|
||||
]}
|
||||
sprints={resultSprints}
|
||||
/>
|
||||
</Scroller>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Ref, WithLookup } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Issue, Sprint } from '@hcengineering/tracker'
|
||||
import { Issue, IssueStatus, Sprint } from '@hcengineering/tracker'
|
||||
import { ButtonKind, ButtonShape, ButtonSize, Label, tooltip } from '@hcengineering/ui'
|
||||
import DatePresenter from '@hcengineering/ui/src/components/calendar/DatePresenter.svelte'
|
||||
import { activeSprint } from '../../issues'
|
||||
@@ -61,15 +61,41 @@
|
||||
$: ids = new Set(issues?.map((it) => it._id) ?? [])
|
||||
|
||||
$: noParents = issues?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
|
||||
|
||||
const statuses = createQuery()
|
||||
let issueStatuses: Map<Ref<IssueStatus>, WithLookup<IssueStatus>> = new Map()
|
||||
$: if (noParents !== undefined) {
|
||||
statuses.query(tracker.class.IssueStatus, { _id: { $in: Array.from(noParents.map((it) => it.status)) } }, (res) => {
|
||||
issueStatuses = new Map(res.map((it) => [it._id, it]))
|
||||
})
|
||||
} else {
|
||||
statuses.unsubscribe()
|
||||
}
|
||||
|
||||
$: totalEstimation = (noParents ?? [{ estimation: 0, childInfo: [] } as unknown as Issue])
|
||||
.map((it) => {
|
||||
const cat = issueStatuses.get(it.status)?.category
|
||||
|
||||
let retEst = it.estimation
|
||||
if (it.childInfo?.length > 0) {
|
||||
const cEstimation = it.childInfo.map((ct) => ct.estimation).reduce((a, b) => a + b, 0)
|
||||
const cReported = it.childInfo.map((ct) => ct.reportedTime).reduce((a, b) => a + b, 0)
|
||||
if (cEstimation !== 0) {
|
||||
return cEstimation
|
||||
retEst = cEstimation
|
||||
if (cat === tracker.issueStatusCategory.Completed || cat === tracker.issueStatusCategory.Canceled) {
|
||||
if (cReported < cEstimation) {
|
||||
retEst = cReported
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cat === tracker.issueStatusCategory.Completed || cat === tracker.issueStatusCategory.Canceled) {
|
||||
if (it.reportedTime < it.estimation) {
|
||||
return it.reportedTime
|
||||
}
|
||||
}
|
||||
}
|
||||
return it.estimation
|
||||
return retEst
|
||||
})
|
||||
.reduce((it, cur) => {
|
||||
return it + cur
|
||||
@@ -79,7 +105,7 @@
|
||||
if (it.childInfo?.length > 0) {
|
||||
const cReported = it.childInfo.map((ct) => ct.reportedTime).reduce((a, b) => a + b, 0)
|
||||
if (cReported !== 0) {
|
||||
return cReported
|
||||
return cReported + it.reportedTime
|
||||
}
|
||||
}
|
||||
return it.reportedTime
|
||||
@@ -114,7 +140,7 @@
|
||||
{onlyIcon}
|
||||
{enlargedText}
|
||||
value={value.sprint}
|
||||
onSprintIdChange={handleSprintIdChanged}
|
||||
onChange={handleSprintIdChanged}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -14,20 +14,21 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Class, Doc, FindOptions, getObjectValue, Ref } from '@hcengineering/core'
|
||||
import { Class, Doc, FindOptions, getObjectValue, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Issue, Sprint } from '@hcengineering/tracker'
|
||||
import { CheckBox, Spinner, tooltip } from '@hcengineering/ui'
|
||||
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: Sprint[] | undefined = undefined
|
||||
export let sprints: WithLookup<Sprint>[] | undefined = undefined
|
||||
export let loadingProps: LoadingProps | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -46,6 +47,12 @@
|
||||
$: 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 })
|
||||
}
|
||||
@@ -90,66 +97,97 @@
|
||||
|
||||
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 sprints 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) ?? ''}
|
||||
projectId={docObject._id}
|
||||
{...attributeModel.props}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{#each Array.from(byProject?.entries() ?? []) as e}
|
||||
<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) ?? ''}
|
||||
projectId={docObject._id}
|
||||
{...attributeModel.props}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</ExpandCollapse>
|
||||
{/each}
|
||||
{:else if loadingProps !== undefined}
|
||||
{#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex}
|
||||
@@ -173,6 +211,17 @@
|
||||
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;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import type { Class, Doc, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { BuildModelKey } from '@hcengineering/view'
|
||||
import {
|
||||
@@ -30,7 +30,7 @@
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let itemsConfig: (BuildModelKey | string)[]
|
||||
export let loadingProps: LoadingProps | undefined = undefined
|
||||
export let sprints: Sprint[] = []
|
||||
export let sprints: WithLookup<Sprint>[] = []
|
||||
|
||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||
if (dir === 'vertical') {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<!--
|
||||
// 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 { Ref } from '@hcengineering/core'
|
||||
import { Sprint, Project } from '@hcengineering/tracker'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { ButtonKind, ButtonShape, ButtonSize, tooltip } from '@hcengineering/ui'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import tracker from '../../plugin'
|
||||
import ProjectSelector from '../ProjectSelector.svelte'
|
||||
import { activeProject } from '../../issues'
|
||||
|
||||
export let value: Sprint
|
||||
export let isEditable: boolean = true
|
||||
export let shouldShowLabel: boolean = true
|
||||
export let popupPlaceholder: IntlString = tracker.string.MoveToProject
|
||||
export let shouldShowPlaceholder = true
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
export let shape: ButtonShape = undefined
|
||||
export let justify: 'left' | 'center' = 'left'
|
||||
export let width: string | undefined = '100%'
|
||||
export let onlyIcon: boolean = false
|
||||
export let groupBy: string | undefined = undefined
|
||||
export let enlargedText = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const handleProjectIdChanged = async (newProjectId: Ref<Project> | null | undefined) => {
|
||||
if (!isEditable || newProjectId === undefined || value.project === newProjectId) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(value, { project: newProjectId ?? undefined })
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if (value.project && value.project !== $activeProject && groupBy !== 'project') || shouldShowPlaceholder}
|
||||
<div
|
||||
class="clear-mins"
|
||||
use:tooltip={{ label: value.project ? tracker.string.MoveToProject : tracker.string.AddToProject }}
|
||||
>
|
||||
<ProjectSelector
|
||||
{kind}
|
||||
{size}
|
||||
{shape}
|
||||
{width}
|
||||
{justify}
|
||||
{isEditable}
|
||||
{shouldShowLabel}
|
||||
{popupPlaceholder}
|
||||
{onlyIcon}
|
||||
{enlargedText}
|
||||
value={value.project}
|
||||
onChange={handleProjectIdChanged}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -16,7 +16,7 @@
|
||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, IntlString, translate } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { Project, Sprint } from '@hcengineering/tracker'
|
||||
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import { Button, ButtonShape, eventToHTMLElement, SelectPopup, showPopup, Label } from '@hcengineering/ui'
|
||||
import tracker from '../../plugin'
|
||||
@@ -25,7 +25,7 @@
|
||||
export let value: Ref<Sprint> | null | undefined
|
||||
export let shouldShowLabel: boolean = true
|
||||
export let isEditable: boolean = true
|
||||
export let onSprintIdChange: ((newSprintId: Ref<Sprint> | undefined) => void) | undefined = undefined
|
||||
export let onChange: ((newSprintId: Ref<Sprint> | undefined) => void) | undefined = undefined
|
||||
export let popupPlaceholder: IntlString = tracker.string.AddToSprint
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'small'
|
||||
@@ -35,6 +35,8 @@
|
||||
export let onlyIcon: boolean = false
|
||||
export let enlargedText = false
|
||||
|
||||
export let useProject: Ref<Project> | undefined = undefined
|
||||
|
||||
let selectedSprint: Sprint | undefined
|
||||
let defaultSprintLabel = ''
|
||||
|
||||
@@ -42,7 +44,7 @@
|
||||
let rawSprints: Sprint[] = []
|
||||
query.query(
|
||||
tracker.class.Sprint,
|
||||
{},
|
||||
useProject ? { project: useProject } : {},
|
||||
(res) => {
|
||||
rawSprints = res
|
||||
},
|
||||
@@ -87,7 +89,7 @@
|
||||
SelectPopup,
|
||||
{ value: sprintInfo, placeholder: popupPlaceholder, searchable: true },
|
||||
eventToHTMLElement(event),
|
||||
onSprintIdChange
|
||||
onChange
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user