mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Issue Status fixes (#2482)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
9daef6444d
commit
673eec8110
@ -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"
|
||||
|
@ -41,6 +41,7 @@
|
||||
</div>
|
||||
<span class="an-element__label title">
|
||||
{#if label}<Label {label} />{/if}
|
||||
<slot name="title" />
|
||||
</span>
|
||||
</div>
|
||||
<slot name="tools" />
|
||||
|
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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),
|
||||
|
@ -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 />
|
||||
|
@ -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>
|
||||
|
@ -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}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 />
|
||||
|
Loading…
Reference in New Issue
Block a user