Issue Status fixes (#2482)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-12-29 13:57:15 +07:00 committed by GitHub
parent 9daef6444d
commit 673eec8110
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 316 additions and 197 deletions

View File

@ -130,7 +130,16 @@
<slot name="content" />
</div>
{:else}
<Button {focusIndex} width={width ?? 'min-content'} {size} {kind} {justify} {showTooltip} on:click={_click}>
<Button
{focusIndex}
disabled={readonly}
width={width ?? 'min-content'}
{size}
{kind}
{justify}
{showTooltip}
on:click={_click}
>
<div
slot="content"
class="overflow-label flex-row-center"

View File

@ -41,6 +41,7 @@
</div>
<span class="an-element__label title">
{#if label}<Label {label} />{/if}
<slot name="title" />
</span>
</div>
<slot name="tools" />

View File

@ -21,7 +21,7 @@
export let value: WithLookup<Issue>
export let disableClick = false
export let onClick: () => void
export let onClick: (() => void) | undefined = undefined
function handleIssueEditorOpened () {
if (disableClick) {

View File

@ -32,12 +32,20 @@
Spinner
} from '@hcengineering/ui'
import { AttributeModel, BuildModelKey } from '@hcengineering/view'
import { buildModel, filterStore, getObjectPresenter, LoadingProps, Menu } from '@hcengineering/view-resources'
import {
buildModel,
filterStore,
FixedColumn,
getObjectPresenter,
LoadingProps,
Menu
} from '@hcengineering/view-resources'
import { onDestroy } from 'svelte'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import { IssuesGroupByKeys, issuesGroupEditorMap, IssuesOrderByKeys, issuesSortOrderMap } from '../../utils'
import CreateIssue from '../CreateIssue.svelte'
import IssueStatistics from '../sprints/IssueStatistics.svelte'
import IssuesListItem from './IssuesListItem.svelte'
export let _class: Ref<Class<Doc>>
@ -79,7 +87,7 @@
let personPresenter: AttributeModel
let isCollapsedMap: Record<any, boolean> = {}
let varsStyle: string = ''
let propsWidth: Record<string, number> = {}
let propsWidth: Record<string, number> = { groupBy: 0 }
let itemModels: AttributeModel[]
let isFilterUpdate = false
let groupedIssuesBeforeFilter = groupedIssues
@ -122,7 +130,7 @@
}
function toCat (category: any): any {
return category ?? noCategory
return 'cat-' + (category ?? noCategory)
}
const handleCollapseCategory = (category: any) => {
@ -155,7 +163,7 @@
onDestroy(unsubscribeFilter)
$: {
if (isFilterUpdate && groupedIssuesBeforeFilter !== groupedIssues) {
if (isFilterUpdate && groupedIssuesBeforeFilter !== groupedIssues && groupByKey) {
isCollapsedMap = {}
categories.forEach((category) => (isCollapsedMap[toCat(category)] = getInitCollapseValue(category)))
@ -195,6 +203,10 @@
varsStyle = ''
for (const key in propsWidth) varsStyle += `--fixed-${key}: ${propsWidth[key]}px;`
}
const checkWidth = (key: string, result: CustomEvent): void => {
if (result !== undefined) propsWidth[key] = result.detail
}
</script>
<div class="issueslist-container" style={varsStyle}>
@ -202,40 +214,56 @@
{@const items = groupedIssues[category] ?? []}
{@const limited = limitGroup(category, groupedIssues, categoryLimit) ?? []}
{#if headerComponent || groupByKey === 'assignee' || category === undefined}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-between categoryHeader row" on:click={() => handleCollapseCategory(toCat(category))}>
<div class="flex-row-center gap-2 clear-mins">
{#if groupByKey === 'assignee' && personPresenter}
<svelte:component
this={personPresenter.presenter}
shouldShowLabel={true}
value={employees.find((x) => x?._id === category)}
defaultName={tracker.string.NoAssignee}
shouldShowPlaceholder={true}
isInteractive={false}
avatarSize={'small'}
enlargedText
{currentSpace}
/>
{:else if !groupByKey}
<span class="text-base fs-bold overflow-label content-accent-color pointer-events-none">
<Label label={tracker.string.NoGrouping} />
</span>
{:else if headerComponent}
<Component
is={headerComponent}
props={{
isEditable: false,
shouldShowLabel: true,
value: groupByKey ? { [groupByKey]: category } : {},
statuses: groupByKey === 'status' ? statuses : undefined,
issues: groupedIssues[category],
width: 'min-content',
kind: 'list-header',
enlargedText: true,
currentSpace
}}
/>
{/if}
<FixedColumn
width={propsWidth.groupBy}
key={'groupBy'}
justify={'left'}
on:update={(result) => checkWidth('groupBy', result)}
>
{#if groupByKey === 'assignee' && personPresenter}
<svelte:component
this={personPresenter.presenter}
shouldShowLabel={true}
value={employees.find((x) => x?._id === category)}
defaultName={tracker.string.NoAssignee}
shouldShowPlaceholder={true}
isInteractive={false}
avatarSize={'small'}
enlargedText
{currentSpace}
/>
{:else if !groupByKey}
<span class="text-base fs-bold overflow-label content-accent-color pointer-events-none">
<Label label={tracker.string.NoGrouping} />
</span>
{:else if headerComponent}
<Component
is={headerComponent}
props={{
isEditable: false,
shouldShowLabel: true,
value: groupByKey ? { [groupByKey]: category } : {},
statuses: groupByKey === 'status' ? statuses : undefined,
issues: groupedIssues[category],
width: 'min-content',
kind: 'list-header',
enlargedText: true,
currentSpace
}}
/>
{/if}
</FixedColumn>
<FixedColumn
width={propsWidth.statistics}
key={'statistics'}
justify={'left'}
on:update={(result) => checkWidth('statistics', result)}
>
<IssueStatistics issues={groupedIssues[category]} />
</FixedColumn>
{#if limited.length < items.length}
<div class="counter">
{limited.length}

View File

@ -17,7 +17,8 @@
import { SortingOrder, WithLookup } from '@hcengineering/core'
import presentation, { Card, createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
import { Button, EditBox, EditStyle, IconAdd, Label, showPopup } from '@hcengineering/ui'
import { Button, EditStyle, eventToHTMLElement, IconAdd, Label, showPopup } from '@hcengineering/ui'
import EditBoxPopup from '@hcengineering/view-resources/src/components/EditBoxPopup.svelte'
import { createEventDispatcher } from 'svelte'
import tracker from '../../../plugin'
import IssuePresenter from '../IssuePresenter.svelte'
@ -26,7 +27,7 @@
import TimeSpendReportPopup from './TimeSpendReportPopup.svelte'
import TimeSpendReports from './TimeSpendReports.svelte'
export let value: string | number | undefined
export let value: number
export let format: 'text' | 'password' | 'number'
export let kind: EditStyle = 'search-style'
export let object: Issue
@ -35,10 +36,6 @@
const dispatch = createEventDispatcher()
function _onkeypress (ev: KeyboardEvent) {
if (ev.key === 'Enter') dispatch('close', _value)
}
$: childIds = Array.from((object.childInfo ?? []).map((it) => it.childId))
const query = createQuery()
@ -78,13 +75,14 @@
)
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<Card
label={tracker.string.Estimation}
canSave={true}
okAction={() => {
dispatch('close', _value)
}}
okLabel={presentation.string.Save}
okLabel={_value !== value ? presentation.string.Save : presentation.string.Close}
on:close={() => {
dispatch('close', null)
}}
@ -92,8 +90,28 @@
<svelte:fragment slot="title">
<div class="flex-row-center">
<Label label={tracker.string.Estimation} />
<div class="ml-2">
<EstimationStatsPresenter value={object} />
<div
class="ml-2 mr-4"
on:click={(evt) => {
showPopup(
EditBoxPopup,
{
value: _value === 0 ? undefined : _value,
format,
kind,
placeholder: tracker.string.Estimation,
maxDigitsAfterPoint: 3
},
eventToHTMLElement(evt),
(res) => {
if (typeof res === 'number') {
_value = res
}
}
)
}}
>
<EstimationStatsPresenter value={object} estimation={_value} />
</div>
</div>
</svelte:fragment>
@ -101,23 +119,9 @@
<svelte:fragment slot="header">
<IssuePresenter value={object} disableClick />
</svelte:fragment>
<div class="header no-border flex-col p-1">
<div class="flex-row-center flex-between">
<EditBox
bind:value={_value}
{format}
{kind}
placeholder={tracker.string.Estimation}
focus
maxDigitsAfterPoint={3}
on:keypress={_onkeypress}
on:change={() => {
if (typeof _value === 'number') {
object.estimation = _value
}
}}
/>
</div>
<div class="flex-row-center flex-between" />
</div>
{#if currentTeam && issueStatuses}
<SubIssuesEstimations

View File

@ -21,6 +21,9 @@
import TimePresenter from './TimePresenter.svelte'
export let value: Issue | AttachedData<Issue>
export let estimation: number | undefined = undefined
$: _estimation = estimation ?? value.estimation
$: workDayLength = value.workDayLength
$: childReportTime = floorFractionDigits(
@ -30,11 +33,12 @@
$: childEstimationTime = (value.childInfo ?? []).map((it) => it.estimation).reduce((a, b) => a + b, 0)
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="estimation-container" on:click>
<div class="icon">
<EstimationProgressCircle
value={Math.max(value.reportedTime, childReportTime)}
max={childEstimationTime || value.estimation}
max={childEstimationTime || _estimation}
/>
</div>
<span class="overflow-label label flex-row-center flex-nowrap text-md">
@ -61,21 +65,21 @@
{/if}
{#if childEstimationTime}
{@const childEstTime = Math.round(childEstimationTime)}
{@const estimationDiff = childEstTime - Math.round(value.estimation)}
{@const estimationDiff = childEstTime - Math.round(_estimation)}
{#if estimationDiff !== 0}
<div class="flex flex-nowrap mr-1" class:showWarning={estimationDiff !== 0}>
<TimePresenter value={childEstTime} {workDayLength} />
</div>
{#if value.estimation !== 0}
{#if _estimation !== 0}
<div class="romColor">
(<TimePresenter value={value.estimation} {workDayLength} />)
(<TimePresenter value={_estimation} {workDayLength} />)
</div>
{/if}
{:else}
<TimePresenter value={value.estimation} {workDayLength} />
<TimePresenter value={_estimation} {workDayLength} />
{/if}
{:else}
<TimePresenter value={value.estimation} {workDayLength} />
<TimePresenter value={_estimation} {workDayLength} />
{/if}
</span>
</div>

View File

@ -34,7 +34,7 @@
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {})
let varsStyle: string = ''
const propsWidth: Record<string, number> = { issue: 0 }
const propsWidth: Record<string, number> = { issue: 0, estimation: 0, assignee: 0 }
$: if (propsWidth) {
varsStyle = ''
for (const key in propsWidth) varsStyle += `--fixed-${key}: ${propsWidth[key]}px;`
@ -59,7 +59,7 @@
listProvider.updateFocus(issue)
}}
>
<div class="flex-row-center clear-mins gap-2 p-2">
<div class="flex-row-center clear-mins gap-2 p-2 flex-grow">
<span class="issuePresenter">
<FixedColumn
width={propsWidth.issue}
@ -76,16 +76,30 @@
{issue.title}
</span>
</div>
<div class="flex-center flex-no-shrink gap-2">
<FixedColumn
width={propsWidth.assignee}
key={'assignee'}
justify={'right'}
on:update={(result) => checkWidth('assignee', result)}
>
<UserBox
width={'100%'}
label={tracker.string.Assignee}
_class={contact.class.Employee}
value={issue.assignee}
readonly
showNavigate={false}
/>
</FixedColumn>
<FixedColumn
width={propsWidth.estimation}
key={'estimation'}
justify={'left'}
on:update={(result) => checkWidth('estimation', result)}
>
<EstimationEditor value={issue} kind={'list'} />
</div>
</FixedColumn>
</div>
</svelte:fragment>
</ListView>

View File

@ -16,7 +16,8 @@
import { Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
import { Label, Scroller, Spinner } from '@hcengineering/ui'
import { Scroller, Spinner } from '@hcengineering/ui'
import Expandable from '@hcengineering/ui/src/components/Expandable.svelte'
import tracker from '../../../plugin'
import EstimationSubIssueList from './EstimationSubIssueList.svelte'
@ -37,12 +38,16 @@
{#if subIssues && issueStatuses}
{#if hasSubIssues}
<Label label={tracker.string.ChildEstimation} />: {total}
<div class="h-50">
<Scroller>
<EstimationSubIssueList issues={subIssues} {teams} />
</Scroller>
</div>
<Expandable label={tracker.string.ChildEstimation}>
<svelte:fragment slot="title">
: {total}
</svelte:fragment>
<div class="h-50">
<Scroller>
<EstimationSubIssueList issues={subIssues} {teams} />
</Scroller>
</div>
</Expandable>
{/if}
{:else}
<div class="flex-center pt-3">

View File

@ -30,7 +30,7 @@
export let value: TimeSpendReport | undefined
export let placeholder: IntlString = tracker.string.TimeSpendReportValue
export let defaultTimeReportDay = TimeReportDayType.PreviousWorkDay
export let defaultTimeReportDay: TimeReportDayType = TimeReportDayType.PreviousWorkDay
const data = {
date: value?.date ?? getTimeReportDate(defaultTimeReportDay),

View File

@ -16,7 +16,7 @@
import { DocumentQuery, Ref, SortingOrder } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, Team, TimeSpendReport } from '@hcengineering/tracker'
import { floorFractionDigits, Label, Scroller, Spinner } from '@hcengineering/ui'
import { Expandable, floorFractionDigits, Label, Scroller, Spinner } from '@hcengineering/ui'
import tracker from '../../../plugin'
import TimePresenter from './TimePresenter.svelte'
import TimeSpendReportsList from './TimeSpendReportsList.svelte'
@ -42,15 +42,19 @@
</script>
{#if reports}
<span class="overflow-label flex-nowrap">
<Label label={tracker.string.ReportedTime} />: <TimePresenter value={reportedTime} {workDayLength} />
<Label label={tracker.string.TimeSpendReports} />: <TimePresenter value={total} {workDayLength} />
</span>
<div class="h-50">
<Scroller>
<TimeSpendReportsList {reports} {teams} />
</Scroller>
</div>
<Expandable expanded={true}>
<svelte:fragment slot="title">
<span class="overflow-label flex-nowrap">
<Label label={tracker.string.ReportedTime} />: <TimePresenter value={reportedTime} {workDayLength} />
<Label label={tracker.string.TimeSpendReports} />: <TimePresenter value={total} {workDayLength} />
</span>
</svelte:fragment>
<div class="h-50">
<Scroller>
<TimeSpendReportsList {reports} {teams} />
</Scroller>
</div>
</Expandable>
{:else}
<div class="flex-center pt-3">
<Spinner />

View File

@ -36,7 +36,7 @@
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {})
let varsStyle: string = ''
const propsWidth: Record<string, number> = { issue: 0 }
const propsWidth: Record<string, number> = { issue: 0, assignee: 0, reported: 0, date: 0 }
$: if (propsWidth) {
varsStyle = ''
for (const key in propsWidth) varsStyle += `--fixed-${key}: ${propsWidth[key]}px;`
@ -82,7 +82,7 @@
}}
on:click={(evt) => editSpendReport(evt, report, currentTeam?.defaultTimeReportDay)}
>
<div class="flex-row-center clear-mins gap-2 p-2">
<div class="flex-row-center clear-mins gap-2 p-2 flex-grow">
<span class="issuePresenter">
<FixedColumn
width={propsWidth.issue}
@ -101,17 +101,40 @@
</span>
{/if}
</div>
<div class="flex-center flex-no-shrink gap-2">
<FixedColumn
width={propsWidth.assignee}
key={'assignee'}
justify={'left'}
on:update={(result) => checkWidth('assignee', result)}
>
<UserBox
width={'100%'}
label={tracker.string.Assignee}
_class={contact.class.Employee}
value={report.employee}
readonly
showNavigate={false}
/>
<TimePresenter value={report.value} workDayLength={currentTeam?.workDayLength} />
</FixedColumn>
<FixedColumn
width={propsWidth.reported}
key={'reported'}
justify={'center'}
on:update={(result) => checkWidth('reported', result)}
>
<div class="p-1">
<TimePresenter value={report.value} workDayLength={currentTeam?.workDayLength} />
</div>
</FixedColumn>
<FixedColumn
width={propsWidth.date}
key={'date'}
justify={'left'}
on:update={(result) => checkWidth('date', result)}
>
<DatePresenter value={report.date} />
</div>
</FixedColumn>
</div>
</svelte:fragment>
</ListView>

View File

@ -0,0 +1,108 @@
<!--
// 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, WithLookup } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, WorkDayLength } from '@hcengineering/tracker'
import { floorFractionDigits, Label } from '@hcengineering/ui'
import tracker from '../../plugin'
import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte'
import TimePresenter from '../issues/timereport/TimePresenter.svelte'
export let issues: Issue[] | undefined = undefined
export let capacity: number | undefined = undefined
export let workDayLength: WorkDayLength = WorkDayLength.EIGHT_HOURS
$: ids = new Set(issues?.map((it) => it._id) ?? [])
$: noParents = issues?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
$: rootNoBacklogIssues = noParents?.filter(
(it) => issueStatuses.get(it.status)?.category !== tracker.issueStatusCategory.Backlog
)
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 = floorFractionDigits(
(rootNoBacklogIssues ?? [{ 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) {
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 retEst
})
.reduce((it, cur) => {
return it + cur
}, 0),
3
)
$: totalReported = floorFractionDigits(
(noParents ?? [{ reportedTime: 0, childInfo: [] } as unknown as Issue])
.map((it) => {
if (it.childInfo?.length > 0) {
const cReported = it.childInfo.map((ct) => ct.reportedTime).reduce((a, b) => a + b, 0)
if (cReported !== 0) {
return cReported + it.reportedTime
}
}
return it.reportedTime
})
.reduce((it, cur) => {
return it + cur
}),
3
)
</script>
{#if issues}
<!-- <Label label={tracker.string.SprintDay} value={}/> -->
<div class="flex-row-center flex-no-shrink h-6" class:showWarning={totalEstimation > (capacity ?? 0)}>
<EstimationProgressCircle value={totalReported} max={totalEstimation} />
<div class="w-2 min-w-2" />
{#if totalReported > 0}
<TimePresenter value={totalReported} {workDayLength} />
/
{/if}
<TimePresenter value={totalEstimation} {workDayLength} />
{#if capacity}
<Label label={tracker.string.CapacityValue} params={{ value: capacity }} />
{/if}
</div>
{/if}

View File

@ -13,17 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, WithLookup } from '@hcengineering/core'
import { Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Issue, IssueStatus, IssueTemplate, Sprint, Team } from '@hcengineering/tracker'
import { ButtonKind, ButtonSize, ButtonShape, floorFractionDigits } from '@hcengineering/ui'
import { Label, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { Issue, IssueTemplate, Sprint, Team } from '@hcengineering/tracker'
import { ButtonKind, ButtonShape, ButtonSize, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import DatePresenter from '@hcengineering/ui/src/components/calendar/DatePresenter.svelte'
import { activeSprint } from '../../issues'
import tracker from '../../plugin'
import { getDayOfSprint } from '../../utils'
import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte'
import TimePresenter from '../issues/timereport/TimePresenter.svelte'
import SprintSelector from './SprintSelector.svelte'
@ -38,7 +36,7 @@
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = '100%'
export let onlyIcon: boolean = false
export let issues: Issue[] | undefined = undefined
export let groupBy: string | undefined = undefined
export let enlargedText: boolean = false
@ -59,75 +57,9 @@
await client.update(value, { sprint: newSprintId })
}
$: ids = new Set(issues?.map((it) => it._id) ?? [])
$: noParents = issues?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
$: rootNoBacklogIssues = noParents?.filter(
(it) => issueStatuses.get(it.status)?.category !== tracker.issueStatusCategory.Backlog
)
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 = floorFractionDigits(
(rootNoBacklogIssues ?? [{ 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) {
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 retEst
})
.reduce((it, cur) => {
return it + cur
}, 0),
3
)
$: totalReported = floorFractionDigits(
(noParents ?? [{ reportedTime: 0, childInfo: [] } as unknown as Issue])
.map((it) => {
if (it.childInfo?.length > 0) {
const cReported = it.childInfo.map((ct) => ct.reportedTime).reduce((a, b) => a + b, 0)
if (cReported !== 0) {
return cReported + it.reportedTime
}
}
return it.reportedTime
})
.reduce((it, cur) => {
return it + cur
}),
3
)
const sprintQuery = createQuery()
let sprint: Sprint | undefined
$: if (issues !== undefined && value.sprint) {
$: if (value.sprint) {
sprintQuery.query(tracker.class.Sprint, { _id: value.sprint }, (res) => {
sprint = res.shift()
})
@ -161,7 +93,7 @@
</div>
{/if}
{#if sprint || issues}
{#if sprint}
<div class="flex-row-center" class:minus-margin-space={kind === 'list-header'} class:text-sm={twoRows}>
{#if sprint}
{@const now = Date.now()}
@ -181,26 +113,6 @@
/
<TimePresenter value={sprintDaysTo} {workDayLength} />
{/if}
{#if issues}
<!-- <Label label={tracker.string.SprintDay} value={}/> -->
<div
class="flex-row-center flex-no-shrink h-6"
class:ml-2={sprint}
class:ml-0-5={!sprint}
class:showWarning={totalEstimation > (sprint?.capacity ?? 0)}
>
<EstimationProgressCircle value={totalReported} max={totalEstimation} />
<div class="w-2 min-w-2" />
{#if totalReported > 0}
<TimePresenter value={totalReported} {workDayLength} />
/
{/if}
<TimePresenter value={totalEstimation} {workDayLength} />
{#if sprint?.capacity}
<Label label={tracker.string.CapacityValue} params={{ value: sprint?.capacity }} />
{/if}
</div>
{/if}
</div>
{/if}
</div>

View File

@ -16,7 +16,7 @@
<script lang="ts">
import type { IntlString } from '@hcengineering/platform'
import { createEventDispatcher } from 'svelte'
import { EditBox, resizeObserver } from '@hcengineering/ui'
import { Button, EditBox, IconCheck, resizeObserver } from '@hcengineering/ui'
import type { EditStyle } from '@hcengineering/ui'
export let value: string | number | undefined
@ -32,7 +32,12 @@
</script>
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="header no-border">
<EditBox bind:value {placeholder} {format} {kind} focus on:keypress={_onkeypress} />
<div class="header no-border flex-row-center">
<div class="flex-grow">
<EditBox bind:value {placeholder} {format} {kind} focus on:keypress={_onkeypress} />
</div>
<div class="p-1">
<Button icon={IconCheck} size={'small'} on:click={() => dispatch('close', value)} />
</div>
</div>
</div>

View File

@ -17,14 +17,14 @@
import { resizeObserver } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let width: number = 0
export let width: number | undefined = 0
export let key: string
export let justify: string = ''
const dispatch = createEventDispatcher()
let cWidth: number
$: if (cWidth > width) {
let cWidth: number = 0
$: if (cWidth > (width ?? 0)) {
width = cWidth
dispatch('update', cWidth)
}
@ -34,7 +34,9 @@
class="flex-no-shrink"
style="{justify !== '' ? `text-align: ${justify}; ` : ''} min-width: var(--fixed-{key});"
use:resizeObserver={(element) => {
cWidth = element.clientWidth
if (element.clientWidth > cWidth) {
cWidth = element.clientWidth
}
}}
>
<slot />