mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 04:25:03 +02:00
@@ -0,0 +1,16 @@
|
||||
<span class="root" />
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex-shrink: 10;
|
||||
|
||||
&.with-margin {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,7 +16,7 @@
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import { Class, Doc, FindOptions, getObjectValue, Ref, WithLookup } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import {
|
||||
Button,
|
||||
@@ -36,6 +36,7 @@
|
||||
import tracker from '../../plugin'
|
||||
import { IssuesGroupByKeys, issuesGroupEditorMap, IssuesOrderByKeys, issuesSortOrderMap } from '../../utils'
|
||||
import CreateIssue from '../CreateIssue.svelte'
|
||||
import GrowPresenter from './GrowPresenter.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let currentSpace: Ref<Team> | undefined = undefined
|
||||
@@ -59,10 +60,19 @@
|
||||
lookup: {
|
||||
assignee: contact.class.Employee,
|
||||
status: tracker.class.IssueStatus,
|
||||
space: tracker.class.Team
|
||||
space: tracker.class.Team,
|
||||
_id: {
|
||||
subIssues: tracker.class.Issue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const spaceQuery = createQuery()
|
||||
let currentTeam: Team | undefined
|
||||
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
|
||||
currentTeam = res.shift()
|
||||
})
|
||||
|
||||
let personPresenter: AttributeModel
|
||||
|
||||
$: isCollapsedMap = Object.fromEntries(categories.map((category) => [category, false]))
|
||||
@@ -175,6 +185,7 @@
|
||||
shouldShowPlaceholder={true}
|
||||
isInteractive={false}
|
||||
avatarSize={'x-small'}
|
||||
{currentSpace}
|
||||
/>
|
||||
{:else if headerComponent}
|
||||
<Component
|
||||
@@ -186,7 +197,8 @@
|
||||
statuses: groupByKey === 'status' ? statuses : undefined,
|
||||
issues: groupedIssues[category],
|
||||
size: 'inline',
|
||||
kind: 'list'
|
||||
kind: 'list',
|
||||
currentSpace
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@@ -244,6 +256,8 @@
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
groupBy={groupByKey}
|
||||
{...attributeModel.props}
|
||||
{statuses}
|
||||
{currentTeam}
|
||||
/>
|
||||
</div>
|
||||
{:else if attributeModelIndex === 1}
|
||||
@@ -259,10 +273,12 @@
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
groupBy={groupByKey}
|
||||
{...attributeModel.props}
|
||||
{statuses}
|
||||
{currentTeam}
|
||||
/>
|
||||
</FixedColumn>
|
||||
</div>
|
||||
{:else if attributeModelIndex === 3}
|
||||
{:else if attributeModelIndex === 3 || attributeModel.presenter === GrowPresenter}
|
||||
<svelte:component
|
||||
this={attributeModel.presenter}
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
@@ -281,6 +297,8 @@
|
||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||
groupBy={groupByKey}
|
||||
{...attributeModel.props}
|
||||
{statuses}
|
||||
{currentTeam}
|
||||
/>
|
||||
</FixedColumn>
|
||||
{:else}
|
||||
@@ -291,6 +309,8 @@
|
||||
issueId={docObject._id}
|
||||
groupBy={groupByKey}
|
||||
{...attributeModel.props}
|
||||
{statuses}
|
||||
{currentTeam}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -367,12 +387,7 @@
|
||||
|
||||
.priorityPresenter,
|
||||
.issuePresenter {
|
||||
min-width: 0;
|
||||
// min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
// .grow-cell {
|
||||
// flex-grow: 1;
|
||||
// flex-shrink: 0;
|
||||
// min-width: 0;
|
||||
// }
|
||||
</style>
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
</div>
|
||||
<div class="buttons-group xsmall-gap states-bar">
|
||||
{#if issue && issueStatuses && issue.subIssues > 0}
|
||||
<SubIssuesSelector {issue} {currentTeam} {issueStatuses} />
|
||||
<SubIssuesSelector value={issue} {currentTeam} statuses={issueStatuses} />
|
||||
{/if}
|
||||
<PriorityEditor value={issue} isEditable={true} kind={'link-bordered'} size={'inline'} justify={'center'} />
|
||||
<ProjectEditor
|
||||
|
||||
@@ -58,7 +58,10 @@
|
||||
assignee: contact.class.Employee,
|
||||
status: tracker.class.IssueStatus,
|
||||
space: tracker.class.Team,
|
||||
sprint: tracker.class.Sprint
|
||||
sprint: tracker.class.Sprint,
|
||||
_id: {
|
||||
subIssues: tracker.class.Issue
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
<style lang="scss">
|
||||
.root {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
flex-grow: 0;
|
||||
min-width: 7rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex-shrink: 10;
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
import tracker from '../../../plugin'
|
||||
import { getIssueId } from '../../../issues'
|
||||
|
||||
export let issue: WithLookup<Issue>
|
||||
export let value: WithLookup<Issue>
|
||||
export let currentTeam: Team | undefined
|
||||
export let issueStatuses: WithLookup<IssueStatus>[] | undefined
|
||||
export let statuses: WithLookup<IssueStatus>[] | undefined
|
||||
|
||||
export let kind: ButtonKind = 'link-bordered'
|
||||
export let size: ButtonSize = 'inline'
|
||||
@@ -36,18 +36,18 @@
|
||||
let doneStatus: Ref<Doc> | undefined
|
||||
let countComplate: number = 0
|
||||
|
||||
$: if (issue.$lookup?.subIssues !== undefined) {
|
||||
subIssues = issue.$lookup.subIssues as Issue[]
|
||||
subIssues.sort((a, b) => a.rank.localeCompare(b.rank))
|
||||
$: if (value.$lookup?.subIssues !== undefined) {
|
||||
subIssues = value.$lookup.subIssues as Issue[]
|
||||
subIssues.sort((a, b) => (a.rank ?? '').localeCompare(b.rank ?? ''))
|
||||
}
|
||||
$: if (issueStatuses && subIssues) {
|
||||
doneStatus = issueStatuses.find((s) => s.category === tracker.issueStatusCategory.Completed)?._id ?? undefined
|
||||
$: if (statuses && subIssues) {
|
||||
doneStatus = statuses.find((s) => s.category === tracker.issueStatusCategory.Completed)?._id ?? undefined
|
||||
if (doneStatus) countComplate = subIssues.filter((si) => si.status === doneStatus).length
|
||||
}
|
||||
$: hasSubIssues = (subIssues?.length ?? 0) > 0
|
||||
|
||||
function getIssueStatusIcon (issue: Issue) {
|
||||
const status = issueStatuses?.find((s) => issue.status === s._id)
|
||||
const status = statuses?.find((s) => issue.status === s._id)
|
||||
const category = status?.$lookup?.category
|
||||
const color = status?.color ?? category?.color
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
}
|
||||
|
||||
function openIssue (target: Ref<Issue>) {
|
||||
if (target !== issue._id) {
|
||||
showPanel(tracker.component.EditIssue, target, issue._class, 'content')
|
||||
if (target !== value._id) {
|
||||
showPanel(tracker.component.EditIssue, target, value._class, 'content')
|
||||
}
|
||||
}
|
||||
function showSubIssues () {
|
||||
@@ -71,7 +71,7 @@
|
||||
value: subIssues.map((iss) => {
|
||||
const text = currentTeam ? `${getIssueId(currentTeam, iss)} ${iss.title}` : iss.title
|
||||
|
||||
return { id: iss._id, text, isSelected: iss._id === issue._id, ...getIssueStatusIcon(iss) }
|
||||
return { id: iss._id, text, isSelected: iss._id === value._id, ...getIssueStatusIcon(iss) }
|
||||
}),
|
||||
width: 'large'
|
||||
},
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
function hourFloor (value: number): number {
|
||||
const days = Math.ceil(value)
|
||||
const hours = value - days
|
||||
return days + Math.floor(hours * 10) / 10
|
||||
return days + Math.floor(hours * 100) / 100
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+13
-11
@@ -44,17 +44,19 @@
|
||||
style:stroke-dasharray={lenghtC}
|
||||
style:stroke-dashoffset={dashOffset === 0 ? 0 : dashOffset + 3}
|
||||
/>
|
||||
<circle
|
||||
cx={8}
|
||||
cy={8}
|
||||
r={7}
|
||||
class="progress-circle"
|
||||
style:stroke={primary ? 'var(--primary-bg-color)' : color}
|
||||
style:opacity={dashOffset === 0 ? 0 : 1}
|
||||
style:transform={'rotate(-82deg)'}
|
||||
style:stroke-dasharray={lenghtC}
|
||||
style:stroke-dashoffset={dashOffset === 0 ? lenghtC : lenghtC - dashOffset + 1}
|
||||
/>
|
||||
{#if min !== max && min !== value}
|
||||
<circle
|
||||
cx={8}
|
||||
cy={8}
|
||||
r={7}
|
||||
class="progress-circle"
|
||||
style:stroke={primary ? 'var(--primary-bg-color)' : color}
|
||||
style:opacity={dashOffset === 0 ? 0 : 1}
|
||||
style:transform={'rotate(-82deg)'}
|
||||
style:stroke-dasharray={lenghtC}
|
||||
style:stroke-dashoffset={dashOffset === 0 ? lenghtC : lenghtC - dashOffset + 1}
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@
|
||||
okLabel={value === undefined ? presentation.string.Create : presentation.string.Save}
|
||||
>
|
||||
<div class="flex-row-center gap-2">
|
||||
<EditBox bind:value={data.value} {placeholder} format={'number'} kind={'editbox'} {maxWidth} focus />
|
||||
<EditBox focus bind:value={data.value} {placeholder} format={'number'} kind={'editbox'} {maxWidth} />
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
label={contact.string.Employee}
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
import { Project } from '@anticrm/tracker'
|
||||
import { Button, EditBox, Icon, showPopup } from '@anticrm/ui'
|
||||
import { DocAttributeBar } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { activeProject } from '../../issues'
|
||||
import tracker from '../../plugin'
|
||||
import IssuesView from '../issues/IssuesView.svelte'
|
||||
import ProjectPopup from './ProjectPopup.svelte'
|
||||
import { activeProject } from '../../issues'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let project: Project
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function change (field: string, value: any) {
|
||||
await client.update(project, { [field]: value })
|
||||
@@ -21,6 +22,7 @@
|
||||
showPopup(ProjectPopup, { _class: tracker.class.Project }, evt.target as HTMLElement, (value) => {
|
||||
if (value != null) {
|
||||
project = value
|
||||
dispatch('project', project._id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
export let justify: 'left' | 'center' = 'left'
|
||||
export let width: string | undefined = '100%'
|
||||
export let onlyIcon: boolean = false
|
||||
export let groupBy: string | undefined
|
||||
export let groupBy: string | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Project } from '@anticrm/tracker'
|
||||
import { closePopup, closeTooltip, location } from '@anticrm/ui'
|
||||
import { closePopup, closeTooltip, getCurrentLocation, location, navigate } from '@anticrm/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import { ProjectsViewMode } from '../../utils'
|
||||
@@ -43,10 +43,8 @@
|
||||
|
||||
const projectQuery = createQuery()
|
||||
$: if (projectId !== undefined) {
|
||||
console.log('call query for', projectId)
|
||||
projectQuery.query(tracker.class.Project, { _id: projectId }, (result) => {
|
||||
project = result.shift()
|
||||
console.log('recieve result for', projectId, project)
|
||||
})
|
||||
} else {
|
||||
projectQuery.unsubscribe()
|
||||
@@ -55,7 +53,14 @@
|
||||
</script>
|
||||
|
||||
{#if project}
|
||||
<EditProject {project} />
|
||||
<EditProject
|
||||
{project}
|
||||
on:project={(evt) => {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[5] = evt.detail
|
||||
navigate(loc)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<ProjectBrowser {label} {query} {search} {mode} />
|
||||
{/if}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Issue, Sprint } from '@anticrm/tracker'
|
||||
import { ButtonKind, ButtonShape, ButtonSize, isWeekend, Label, tooltip } from '@anticrm/ui'
|
||||
import DatePresenter from '@anticrm/ui/src/components/calendar/DatePresenter.svelte'
|
||||
import { activeSprint } from '../../issues'
|
||||
import tracker from '../../plugin'
|
||||
import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte'
|
||||
@@ -58,13 +59,29 @@
|
||||
$: ids = new Set(issues?.map((it) => it._id) ?? [])
|
||||
|
||||
$: noParents = issues?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
|
||||
$: totalEstimation = (noParents ?? [{ estimation: 0 }])
|
||||
.map((it) => it.estimation)
|
||||
$: totalEstimation = (noParents ?? [{ estimation: 0, childInfo: [] } as unknown as Issue])
|
||||
.map((it) => {
|
||||
if (it.childInfo?.length > 0) {
|
||||
const cEstimation = it.childInfo.map((ct) => ct.estimation).reduce((a, b) => a + b, 0)
|
||||
if (cEstimation !== 0) {
|
||||
return cEstimation
|
||||
}
|
||||
}
|
||||
return it.estimation
|
||||
})
|
||||
.reduce((it, cur) => {
|
||||
return it + cur
|
||||
})
|
||||
$: totalReported = (noParents ?? [{ reportedTime: 0 }])
|
||||
.map((it) => it.reportedTime)
|
||||
$: totalReported = (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
|
||||
}
|
||||
}
|
||||
return it.reportedTime
|
||||
})
|
||||
.reduce((it, cur) => {
|
||||
return it + cur
|
||||
})
|
||||
@@ -77,11 +94,12 @@
|
||||
})
|
||||
}
|
||||
function getDayOfSprint (startDate: number, now: number): number {
|
||||
const days = Math.floor(Math.abs((1 + now - startDate) / 1000 / 60 / 60 / 24)) + 1
|
||||
const days = Math.floor(Math.abs((1 + now - startDate) / 1000 / 60 / 60 / 24))
|
||||
const stDate = new Date(startDate)
|
||||
const stDateDate = stDate.getDate()
|
||||
const stTime = stDate.getTime()
|
||||
const ds = Array.from(Array(days).keys()).map((it) => stDateDate + it)
|
||||
return ds.filter((it) => !isWeekend(new Date(stDate.setDate(it)))).length
|
||||
return ds.filter((it) => !isWeekend(new Date(new Date(stTime).setDate(it)))).length
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -106,22 +124,29 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if sprint}
|
||||
{@const now = Date.now()}
|
||||
<div class="flex-row-center">
|
||||
<DatePresenter value={sprint.startDate} kind={'transparent'} />
|
||||
<span class="p-1"> / </span><DatePresenter value={sprint.targetDate} kind={'transparent'} />
|
||||
</div>
|
||||
<div class="flex-row-center ml-2">
|
||||
<!-- Active sprint in time -->
|
||||
<Label
|
||||
label={tracker.string.SprintPassed}
|
||||
params={{
|
||||
from:
|
||||
now < sprint.startDate
|
||||
? 0
|
||||
: now > sprint.targetDate
|
||||
? getDayOfSprint(sprint.startDate, sprint.targetDate)
|
||||
: getDayOfSprint(sprint.startDate, now),
|
||||
to: getDayOfSprint(sprint.startDate, sprint.targetDate)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if issues}
|
||||
{#if sprint}
|
||||
{@const now = Date.now()}
|
||||
{#if sprint.startDate < now && now < sprint.targetDate}
|
||||
<!-- Active sprint in time -->
|
||||
<div class="ml-2">
|
||||
<Label
|
||||
label={tracker.string.SprintPassed}
|
||||
params={{
|
||||
from: getDayOfSprint(sprint.startDate, now),
|
||||
to: getDayOfSprint(sprint.startDate, sprint.targetDate) - 1
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- <Label label={tracker.string.SprintDay} value={}/> -->
|
||||
<div class="ml-4 flex-row-center">
|
||||
<div class="mr-2">
|
||||
|
||||
Reference in New Issue
Block a user