mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Tracker: Project - Project selector (#1740)
Signed-off-by: Artyom Grigorovich <grigorovichartyom@gmail.com>
This commit is contained in:
parent
0231234f9a
commit
84844b65a9
@ -148,6 +148,9 @@ export class TIssue extends TDoc implements Issue {
|
|||||||
@Prop(TypeRef(contact.class.Employee), tracker.string.Assignee)
|
@Prop(TypeRef(contact.class.Employee), tracker.string.Assignee)
|
||||||
assignee!: Ref<Employee> | null
|
assignee!: Ref<Employee> | null
|
||||||
|
|
||||||
|
@Prop(TypeRef(tracker.class.Project), tracker.string.Project)
|
||||||
|
project!: Ref<Project> | null
|
||||||
|
|
||||||
@Prop(TypeRef(tracker.class.Issue), tracker.string.Parent)
|
@Prop(TypeRef(tracker.class.Issue), tracker.string.Parent)
|
||||||
parentIssue!: Ref<Issue>
|
parentIssue!: Ref<Issue>
|
||||||
|
|
||||||
@ -212,6 +215,9 @@ export class TProject extends TDoc implements Project {
|
|||||||
@Prop(TypeMarkup(), tracker.string.Project)
|
@Prop(TypeMarkup(), tracker.string.Project)
|
||||||
description?: Markup
|
description?: Markup
|
||||||
|
|
||||||
|
@Prop(TypeString(), tracker.string.AssetLabel)
|
||||||
|
icon!: Asset
|
||||||
|
|
||||||
@Prop(TypeNumber(), tracker.string.Status)
|
@Prop(TypeNumber(), tracker.string.Status)
|
||||||
status!: ProjectStatus
|
status!: ProjectStatus
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
import core, { generateId, Ref, TxOperations } from '@anticrm/core'
|
import core, { generateId, Ref, TxOperations } from '@anticrm/core'
|
||||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
||||||
import { IssueStatus, IssueStatusCategory, Team, genRanks } from '@anticrm/tracker'
|
import { IssueStatus, IssueStatusCategory, Team, genRanks } from '@anticrm/tracker'
|
||||||
|
import { DOMAIN_TRACKER } from '.'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
|
|
||||||
enum DeprecatedIssueStatus {
|
enum DeprecatedIssueStatus {
|
||||||
@ -148,6 +149,36 @@ async function upgradeIssueStatuses (tx: TxOperations): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function migrateIssueProjects (client: MigrationClient): Promise<void> {
|
||||||
|
const issues = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue, project: { $exists: false } })
|
||||||
|
|
||||||
|
if (issues.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const issue of issues) {
|
||||||
|
await client.update(DOMAIN_TRACKER, { _id: issue._id }, { project: null })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upgradeProjectIcons (tx: TxOperations): Promise<void> {
|
||||||
|
const projects = await tx.findAll(tracker.class.Project, {})
|
||||||
|
|
||||||
|
if (projects.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const project of projects) {
|
||||||
|
const icon = project.icon as unknown
|
||||||
|
|
||||||
|
if (icon !== undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.update(project, { icon: tracker.icon.Projects })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createDefaults (tx: TxOperations): Promise<void> {
|
async function createDefaults (tx: TxOperations): Promise<void> {
|
||||||
await createDefaultTeam(tx)
|
await createDefaultTeam(tx)
|
||||||
}
|
}
|
||||||
@ -160,12 +191,19 @@ async function upgradeIssues (tx: TxOperations): Promise<void> {
|
|||||||
await upgradeIssueStatuses(tx)
|
await upgradeIssueStatuses(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function upgradeProjects (tx: TxOperations): Promise<void> {
|
||||||
|
await upgradeProjectIcons(tx)
|
||||||
|
}
|
||||||
|
|
||||||
export const trackerOperation: MigrateOperation = {
|
export const trackerOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient): Promise<void> {},
|
async migrate (client: MigrationClient): Promise<void> {
|
||||||
|
await Promise.all([migrateIssueProjects(client)])
|
||||||
|
},
|
||||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||||
const tx = new TxOperations(client, core.account.System)
|
const tx = new TxOperations(client, core.account.System)
|
||||||
await createDefaults(tx)
|
await createDefaults(tx)
|
||||||
await upgradeTeams(tx)
|
await upgradeTeams(tx)
|
||||||
await upgradeIssues(tx)
|
await upgradeIssues(tx)
|
||||||
|
await upgradeProjects(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import type { Asset, IntlString } from '@anticrm/platform'
|
import type { Asset, IntlString } from '@anticrm/platform'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { registerFocus } from '../focus'
|
import { registerFocus } from '../focus'
|
||||||
import type { AnySvelteComponent, ButtonKind, ButtonSize } from '../types'
|
import type { AnySvelteComponent, ButtonKind, ButtonShape, ButtonSize } from '../types'
|
||||||
import Icon from './Icon.svelte'
|
import Icon from './Icon.svelte'
|
||||||
import Label from './Label.svelte'
|
import Label from './Label.svelte'
|
||||||
import Spinner from './Spinner.svelte'
|
import Spinner from './Spinner.svelte'
|
||||||
@ -25,7 +25,7 @@
|
|||||||
export let labelParams: Record<string, any> = {}
|
export let labelParams: Record<string, any> = {}
|
||||||
export let kind: ButtonKind = 'secondary'
|
export let kind: ButtonKind = 'secondary'
|
||||||
export let size: ButtonSize = 'medium'
|
export let size: ButtonSize = 'medium'
|
||||||
export let shape: 'rectangle' | 'rectangle-left' | 'rectangle-right' | 'circle' | 'round' | undefined = undefined
|
export let shape: ButtonShape = undefined
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
export let justify: 'left' | 'center' = 'center'
|
export let justify: 'left' | 'center' = 'center'
|
||||||
export let disabled: boolean = false
|
export let disabled: boolean = false
|
||||||
|
@ -65,6 +65,7 @@ export type TabModel = Tab[]
|
|||||||
|
|
||||||
export type ButtonKind = 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'link-bordered' | 'dangerous'
|
export type ButtonKind = 'primary' | 'secondary' | 'no-border' | 'transparent' | 'link' | 'link-bordered' | 'dangerous'
|
||||||
export type ButtonSize = 'small' | 'medium' | 'large' | 'x-large'
|
export type ButtonSize = 'small' | 'medium' | 'large' | 'x-large'
|
||||||
|
export type ButtonShape = 'rectangle' | 'rectangle-left' | 'rectangle-right' | 'circle' | 'round' | undefined
|
||||||
export interface PopupPositionElement {
|
export interface PopupPositionElement {
|
||||||
getBoundingClientRect: () => DOMRect
|
getBoundingClientRect: () => DOMRect
|
||||||
position?: {
|
position?: {
|
||||||
|
@ -96,6 +96,10 @@
|
|||||||
"PastMonth": "Past month",
|
"PastMonth": "Past month",
|
||||||
"CopyIssueUrl": "Copy Issue URL to clipboard",
|
"CopyIssueUrl": "Copy Issue URL to clipboard",
|
||||||
"CopyIssueId": "Copy Issue ID to clipboard",
|
"CopyIssueId": "Copy Issue ID to clipboard",
|
||||||
|
"AssetLabel": "Asset",
|
||||||
|
"AddToProject": "Add to project\u2026",
|
||||||
|
"MoveToProject": "Move to project\u2026",
|
||||||
|
"NoProject": "No project",
|
||||||
|
|
||||||
"GotoIssues": "Go to issues",
|
"GotoIssues": "Go to issues",
|
||||||
"GotoActive": "Go to active issues",
|
"GotoActive": "Go to active issues",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import core, { Data, generateId, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
import core, { Data, generateId, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||||
import { Asset, IntlString } from '@anticrm/platform'
|
import { Asset, IntlString } from '@anticrm/platform'
|
||||||
import presentation, { getClient, UserBox, Card, createQuery } from '@anticrm/presentation'
|
import presentation, { getClient, UserBox, Card, createQuery } from '@anticrm/presentation'
|
||||||
import { Issue, IssuePriority, IssueStatus, Team, calcRank } from '@anticrm/tracker'
|
import { Issue, IssuePriority, IssueStatus, Team, calcRank, Project } from '@anticrm/tracker'
|
||||||
import { StyledTextBox } from '@anticrm/text-editor'
|
import { StyledTextBox } from '@anticrm/text-editor'
|
||||||
import {
|
import {
|
||||||
EditBox,
|
EditBox,
|
||||||
@ -32,20 +32,23 @@
|
|||||||
import tracker from '../plugin'
|
import tracker from '../plugin'
|
||||||
import StatusSelector from './StatusSelector.svelte'
|
import StatusSelector from './StatusSelector.svelte'
|
||||||
import PrioritySelector from './PrioritySelector.svelte'
|
import PrioritySelector from './PrioritySelector.svelte'
|
||||||
|
import ProjectSelector from './ProjectSelector.svelte'
|
||||||
|
|
||||||
export let space: Ref<Team>
|
export let space: Ref<Team>
|
||||||
export let parent: Ref<Issue> | undefined
|
export let parent: Ref<Issue> | undefined
|
||||||
export let status: Ref<IssueStatus> | undefined = undefined
|
export let status: Ref<IssueStatus> | undefined = undefined
|
||||||
export let priority: IssuePriority = IssuePriority.NoPriority
|
export let priority: IssuePriority = IssuePriority.NoPriority
|
||||||
export let assignee: Ref<Employee> | null = null
|
export let assignee: Ref<Employee> | null = null
|
||||||
|
export let project: Ref<Project> | null = null
|
||||||
|
|
||||||
let currentAssignee: Ref<Employee> | null = assignee
|
let currentAssignee: Ref<Employee> | null = assignee
|
||||||
let issueStatuses: WithLookup<IssueStatus>[] = []
|
let issueStatuses: WithLookup<IssueStatus>[] = []
|
||||||
|
|
||||||
const object: Data<Issue> = {
|
let object: Data<Issue> = {
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
assignee: null,
|
assignee: null,
|
||||||
|
project: project,
|
||||||
number: 0,
|
number: 0,
|
||||||
rank: '',
|
rank: '',
|
||||||
status: '' as Ref<IssueStatus>,
|
status: '' as Ref<IssueStatus>,
|
||||||
@ -62,6 +65,7 @@
|
|||||||
$: _space = space
|
$: _space = space
|
||||||
$: _parent = parent
|
$: _parent = parent
|
||||||
$: updateIssueStatusId(space, status)
|
$: updateIssueStatusId(space, status)
|
||||||
|
|
||||||
$: statusesQuery.query(
|
$: statusesQuery.query(
|
||||||
tracker.class.IssueStatus,
|
tracker.class.IssueStatus,
|
||||||
{ attachedTo: space },
|
{ attachedTo: space },
|
||||||
@ -73,6 +77,7 @@
|
|||||||
sort: { rank: SortingOrder.Ascending }
|
sort: { rank: SortingOrder.Ascending }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
$: canSave = getTitle(object.title ?? '').length > 0
|
$: canSave = getTitle(object.title ?? '').length > 0
|
||||||
|
|
||||||
async function updateIssueStatusId (teamId: Ref<Team>, issueStatusId?: Ref<IssueStatus>) {
|
async function updateIssueStatusId (teamId: Ref<Team>, issueStatusId?: Ref<IssueStatus>) {
|
||||||
@ -125,6 +130,7 @@
|
|||||||
title: getTitle(object.title),
|
title: getTitle(object.title),
|
||||||
description: object.description,
|
description: object.description,
|
||||||
assignee: currentAssignee,
|
assignee: currentAssignee,
|
||||||
|
project: object.project,
|
||||||
number: (incResult as any).object.sequence,
|
number: (incResult as any).object.sequence,
|
||||||
status: object.status,
|
status: object.status,
|
||||||
priority: object.priority,
|
priority: object.priority,
|
||||||
@ -151,9 +157,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleStatusChanged = (statusId: Ref<IssueStatus> | undefined) => {
|
const handleStatusChanged = (statusId: Ref<IssueStatus> | undefined) => {
|
||||||
if (statusId !== undefined) {
|
if (statusId === undefined) {
|
||||||
object.status = statusId
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object.status = statusId
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProjectIdChanged = (projectId: Ref<Project> | null | undefined) => {
|
||||||
|
if (projectId === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
object = { ...object, project: projectId }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -212,13 +228,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
kind="no-border"
|
kind="no-border"
|
||||||
/>
|
/>
|
||||||
<Button
|
<ProjectSelector value={object.project} onProjectIdChange={handleProjectIdChanged} />
|
||||||
label={tracker.string.Project}
|
|
||||||
icon={tracker.icon.Projects}
|
|
||||||
width="min-content"
|
|
||||||
size="small"
|
|
||||||
kind="no-border"
|
|
||||||
/>
|
|
||||||
<DatePresenter bind:value={object.dueDate} editable />
|
<DatePresenter bind:value={object.dueDate} editable />
|
||||||
<Button
|
<Button
|
||||||
icon={tracker.icon.MoreActions}
|
icon={tracker.icon.MoreActions}
|
||||||
|
109
plugins/tracker-resources/src/components/ProjectSelector.svelte
Normal file
109
plugins/tracker-resources/src/components/ProjectSelector.svelte
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<!--
|
||||||
|
// 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, SortingOrder } from '@anticrm/core'
|
||||||
|
import { Project } from '@anticrm/tracker'
|
||||||
|
import { IntlString, translate } from '@anticrm/platform'
|
||||||
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
|
import { Button, showPopup, SelectPopup, eventToHTMLElement, ButtonShape } from '@anticrm/ui'
|
||||||
|
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
||||||
|
import tracker from '../plugin'
|
||||||
|
|
||||||
|
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 popupPlaceholder: IntlString = tracker.string.AddToProject
|
||||||
|
export let kind: ButtonKind = 'no-border'
|
||||||
|
export let size: ButtonSize = 'small'
|
||||||
|
export let shape: ButtonShape = undefined
|
||||||
|
export let justify: 'left' | 'center' = 'center'
|
||||||
|
export let width: string | undefined = 'min-content'
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const projectsQuery = createQuery()
|
||||||
|
|
||||||
|
let projects: Project[] = []
|
||||||
|
let selectedProject: Project | undefined
|
||||||
|
let defaultProjectLabel = ''
|
||||||
|
|
||||||
|
$: projectsQuery.query(
|
||||||
|
tracker.class.Project,
|
||||||
|
{},
|
||||||
|
(currentProjects) => {
|
||||||
|
projects = currentProjects
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { modifiedOn: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
$: if (value !== undefined) {
|
||||||
|
handleSelectedProjectIdUpdated(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
$: translate(tracker.string.Project, {}).then((result) => (defaultProjectLabel = result))
|
||||||
|
$: projectIcon = selectedProject?.icon ?? tracker.icon.Projects
|
||||||
|
$: projectText = shouldShowLabel ? selectedProject?.label ?? defaultProjectLabel : undefined
|
||||||
|
|
||||||
|
$: projectsInfo = [
|
||||||
|
{ id: null, icon: tracker.icon.Projects, label: tracker.string.NoProject },
|
||||||
|
...projects.map((p) => ({
|
||||||
|
id: p._id,
|
||||||
|
icon: p.icon,
|
||||||
|
text: p.label
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleSelectedProjectIdUpdated = async (newProjectId: Ref<Project> | null) => {
|
||||||
|
if (newProjectId === null) {
|
||||||
|
selectedProject = undefined
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedProject = await client.findOne(tracker.class.Project, { _id: newProjectId })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProjectEditorOpened = (event: MouseEvent) => {
|
||||||
|
if (!isEditable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showPopup(
|
||||||
|
SelectPopup,
|
||||||
|
{ value: projectsInfo, placeholder: popupPlaceholder, searchable: true },
|
||||||
|
eventToHTMLElement(event),
|
||||||
|
onProjectIdChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
{kind}
|
||||||
|
{size}
|
||||||
|
{shape}
|
||||||
|
{width}
|
||||||
|
{justify}
|
||||||
|
icon={projectIcon}
|
||||||
|
disabled={!isEditable}
|
||||||
|
on:click={handleProjectEditorOpened}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="content">
|
||||||
|
{#if projectText}
|
||||||
|
<span class="nowrap">{projectText}</span>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
</Button>
|
@ -161,7 +161,7 @@
|
|||||||
{object.title}
|
{object.title}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex gap-2 mt-2 mb-2">
|
<div class="flex gap-2 mt-2 mb-2">
|
||||||
<PriorityEditor value={issue} {currentSpace} isEditable={true} />
|
<PriorityEditor value={issue} isEditable={true} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -13,15 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, Timestamp, WithLookup } from '@anticrm/core'
|
import { Timestamp, WithLookup } from '@anticrm/core'
|
||||||
import { Issue, Team } from '@anticrm/tracker'
|
import { Issue } from '@anticrm/tracker'
|
||||||
import { DatePresenter, Tooltip, getDaysDifference } from '@anticrm/ui'
|
import { DatePresenter, Tooltip, getDaysDifference } from '@anticrm/ui'
|
||||||
import { getClient } from '@anticrm/presentation'
|
import { getClient } from '@anticrm/presentation'
|
||||||
import DueDatePopup from './DueDatePopup.svelte'
|
import DueDatePopup from './DueDatePopup.svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let value: WithLookup<Issue>
|
export let value: WithLookup<Issue>
|
||||||
export let currentSpace: Ref<Team> | undefined = undefined
|
|
||||||
|
|
||||||
const WARNING_DAYS = 7
|
const WARNING_DAYS = 7
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -37,17 +36,11 @@
|
|||||||
const handleDueDateChanged = async (event: CustomEvent<Timestamp>) => {
|
const handleDueDateChanged = async (event: CustomEvent<Timestamp>) => {
|
||||||
const newDate = event.detail
|
const newDate = event.detail
|
||||||
|
|
||||||
if (newDate === undefined) {
|
if (newDate === undefined || value.dueDate === newDate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentIssue = await client.findOne(tracker.class.Issue, { space: currentSpace, _id: value._id })
|
await client.update(value, { dueDate: newDate })
|
||||||
|
|
||||||
if (currentIssue === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.update(currentIssue, { dueDate: newDate })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIconModifier = (isOverdue: boolean, daysDifference: number | null) => {
|
const getIconModifier = (isOverdue: boolean, daysDifference: number | null) => {
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
import { Class, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
import { Class, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||||
// import Card from '../Card.svelte'
|
|
||||||
import { Panel } from '@anticrm/panel'
|
import { Panel } from '@anticrm/panel'
|
||||||
import { createQuery, getClient, UserBox } from '@anticrm/presentation'
|
import { createQuery, getClient, UserBox } from '@anticrm/presentation'
|
||||||
import { StyledTextBox } from '@anticrm/text-editor'
|
import { StyledTextBox } from '@anticrm/text-editor'
|
||||||
@ -34,15 +33,16 @@
|
|||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import IssuePresenter from './IssuePresenter.svelte'
|
import IssuePresenter from './IssuePresenter.svelte'
|
||||||
import PriorityEditor from './PriorityEditor.svelte'
|
import PriorityEditor from './PriorityEditor.svelte'
|
||||||
|
import ProjectEditor from './ProjectEditor.svelte'
|
||||||
import StatusEditor from './StatusEditor.svelte'
|
import StatusEditor from './StatusEditor.svelte'
|
||||||
|
|
||||||
export let _id: Ref<Issue>
|
export let _id: Ref<Issue>
|
||||||
export let _class: Ref<Class<Issue>>
|
export let _class: Ref<Class<Issue>>
|
||||||
|
|
||||||
const query = createQuery()
|
|
||||||
const statusesQuery = createQuery()
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
const query = createQuery()
|
||||||
|
const statusesQuery = createQuery()
|
||||||
|
|
||||||
let issue: Issue | undefined
|
let issue: Issue | undefined
|
||||||
let currentTeam: Team | undefined
|
let currentTeam: Team | undefined
|
||||||
@ -173,13 +173,11 @@
|
|||||||
<span class="label">
|
<span class="label">
|
||||||
<Label label={tracker.string.Status} />
|
<Label label={tracker.string.Status} />
|
||||||
</span>
|
</span>
|
||||||
<StatusEditor value={issue} statuses={issueStatuses} currentSpace={currentTeam._id} shouldShowLabel />
|
<StatusEditor value={issue} statuses={issueStatuses} shouldShowLabel />
|
||||||
|
|
||||||
<span class="label">
|
<span class="label">
|
||||||
<Label label={tracker.string.Priority} />
|
<Label label={tracker.string.Priority} />
|
||||||
</span>
|
</span>
|
||||||
<PriorityEditor value={issue} currentSpace={currentTeam._id} shouldShowLabel />
|
<PriorityEditor value={issue} shouldShowLabel />
|
||||||
|
|
||||||
<span class="label">
|
<span class="label">
|
||||||
<Label label={tracker.string.Assignee} />
|
<Label label={tracker.string.Assignee} />
|
||||||
</span>
|
</span>
|
||||||
@ -214,18 +212,9 @@
|
|||||||
<span class="label">
|
<span class="label">
|
||||||
<Label label={tracker.string.Project} />
|
<Label label={tracker.string.Project} />
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<ProjectEditor value={issue} />
|
||||||
label={tracker.string.Project}
|
|
||||||
icon={tracker.icon.Projects}
|
|
||||||
size={'large'}
|
|
||||||
kind={'link'}
|
|
||||||
width={'100%'}
|
|
||||||
justify={'left'}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if issue.dueDate !== null}
|
{#if issue.dueDate !== null}
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
|
|
||||||
<span class="label">
|
<span class="label">
|
||||||
<Label label={tracker.string.DueDate} />
|
<Label label={tracker.string.DueDate} />
|
||||||
</span>
|
</span>
|
||||||
@ -241,13 +230,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
kind="no-border"
|
kind="no-border"
|
||||||
/>
|
/>
|
||||||
<Button
|
<ProjectEditor value={issue} size={'small'} kind={'no-border'} width={'min-content'} />
|
||||||
label={tracker.string.Project}
|
|
||||||
icon={tracker.icon.Projects}
|
|
||||||
width="min-content"
|
|
||||||
size="small"
|
|
||||||
kind="no-border"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -478,11 +478,16 @@
|
|||||||
{employees}
|
{employees}
|
||||||
categories={displayedCategories}
|
categories={displayedCategories}
|
||||||
itemsConfig={[
|
itemsConfig={[
|
||||||
{ key: '', presenter: tracker.component.PriorityEditor, props: { currentSpace } },
|
{ key: '', presenter: tracker.component.PriorityEditor },
|
||||||
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
|
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
|
||||||
{ key: '', presenter: tracker.component.StatusEditor, props: { currentSpace, statuses } },
|
{ key: '', presenter: tracker.component.StatusEditor, props: { statuses } },
|
||||||
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true } },
|
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true } },
|
||||||
{ key: '', presenter: tracker.component.DueDatePresenter, props: { currentSpace } },
|
{ key: '', presenter: tracker.component.DueDatePresenter },
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.ProjectEditor,
|
||||||
|
props: { kind: 'secondary', size: 'small', shape: 'round', shouldShowPlaceholder: false }
|
||||||
|
},
|
||||||
{ key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter },
|
{ key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter },
|
||||||
{
|
{
|
||||||
key: '$lookup.assignee',
|
key: '$lookup.assignee',
|
||||||
|
@ -13,8 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref } from '@anticrm/core'
|
import { Issue, IssuePriority } from '@anticrm/tracker'
|
||||||
import { Issue, IssuePriority, Team } from '@anticrm/tracker'
|
|
||||||
import { getClient } from '@anticrm/presentation'
|
import { getClient } from '@anticrm/presentation'
|
||||||
import { Tooltip } from '@anticrm/ui'
|
import { Tooltip } from '@anticrm/ui'
|
||||||
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
||||||
@ -22,7 +21,6 @@
|
|||||||
import PrioritySelector from '../PrioritySelector.svelte'
|
import PrioritySelector from '../PrioritySelector.svelte'
|
||||||
|
|
||||||
export let value: Issue
|
export let value: Issue
|
||||||
export let currentSpace: Ref<Team> | undefined = undefined
|
|
||||||
export let isEditable: boolean = true
|
export let isEditable: boolean = true
|
||||||
export let shouldShowLabel: boolean = false
|
export let shouldShowLabel: boolean = false
|
||||||
|
|
||||||
@ -34,17 +32,11 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
const handlePriorityChanged = async (newPriority: IssuePriority | undefined) => {
|
const handlePriorityChanged = async (newPriority: IssuePriority | undefined) => {
|
||||||
if (!isEditable || newPriority === undefined) {
|
if (!isEditable || newPriority === undefined || value.priority === newPriority) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentIssue = await client.findOne(tracker.class.Issue, { space: currentSpace, _id: value._id })
|
await client.update(value, { priority: newPriority })
|
||||||
|
|
||||||
if (currentIssue === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.update(currentIssue, { priority: newPriority })
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
<!--
|
||||||
|
// 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 '@anticrm/core'
|
||||||
|
import { Issue, Project } from '@anticrm/tracker'
|
||||||
|
import { getClient } from '@anticrm/presentation'
|
||||||
|
import type { ButtonKind, ButtonShape, ButtonSize } from '@anticrm/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import ProjectSelector from '../ProjectSelector.svelte'
|
||||||
|
import { IntlString } from '@anticrm/platform'
|
||||||
|
|
||||||
|
export let value: Issue
|
||||||
|
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%'
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value.project || shouldShowPlaceholder}
|
||||||
|
<ProjectSelector
|
||||||
|
{kind}
|
||||||
|
{size}
|
||||||
|
{shape}
|
||||||
|
{width}
|
||||||
|
{justify}
|
||||||
|
{isEditable}
|
||||||
|
{shouldShowLabel}
|
||||||
|
{popupPlaceholder}
|
||||||
|
value={value.project}
|
||||||
|
onProjectIdChange={handleProjectIdChanged}
|
||||||
|
/>
|
||||||
|
{/if}
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, WithLookup } from '@anticrm/core'
|
import { Ref, WithLookup } from '@anticrm/core'
|
||||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
import { Issue, IssueStatus } from '@anticrm/tracker'
|
||||||
import { getClient } from '@anticrm/presentation'
|
import { getClient } from '@anticrm/presentation'
|
||||||
import { Tooltip } from '@anticrm/ui'
|
import { Tooltip } from '@anticrm/ui'
|
||||||
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
||||||
@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
export let value: Issue
|
export let value: Issue
|
||||||
export let statuses: WithLookup<IssueStatus>[]
|
export let statuses: WithLookup<IssueStatus>[]
|
||||||
export let currentSpace: Ref<Team> | undefined = undefined
|
|
||||||
export let isEditable: boolean = true
|
export let isEditable: boolean = true
|
||||||
export let shouldShowLabel: boolean = false
|
export let shouldShowLabel: boolean = false
|
||||||
|
|
||||||
@ -35,17 +34,11 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
const handleStatusChanged = async (newStatus: Ref<IssueStatus> | undefined) => {
|
const handleStatusChanged = async (newStatus: Ref<IssueStatus> | undefined) => {
|
||||||
if (!isEditable || newStatus === undefined) {
|
if (!isEditable || newStatus === undefined || value.status === newStatus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentIssue = await client.findOne(tracker.class.Issue, { space: currentSpace, _id: value._id })
|
await client.update(value, { status: newStatus })
|
||||||
|
|
||||||
if (currentIssue === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.update(currentIssue, { status: newStatus })
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
import { Project, ProjectStatus, Team } from '@anticrm/tracker'
|
import { Project, ProjectStatus, Team } from '@anticrm/tracker'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import plugin from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import ProjectStatusSelector from './ProjectStatusSelector.svelte'
|
import ProjectStatusSelector from './ProjectStatusSelector.svelte'
|
||||||
|
|
||||||
export let space: Ref<Team>
|
export let space: Ref<Team>
|
||||||
@ -30,6 +30,7 @@
|
|||||||
const object: Data<Project> = {
|
const object: Data<Project> = {
|
||||||
label: '' as IntlString,
|
label: '' as IntlString,
|
||||||
description: '',
|
description: '',
|
||||||
|
icon: tracker.icon.Projects,
|
||||||
status: ProjectStatus.Planned,
|
status: ProjectStatus.Planned,
|
||||||
lead: null,
|
lead: null,
|
||||||
members: [],
|
members: [],
|
||||||
@ -41,25 +42,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onSave () {
|
async function onSave () {
|
||||||
await client.createDoc(plugin.class.Project, space, object)
|
await client.createDoc(tracker.class.Project, space, object)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
label={plugin.string.NewProject}
|
label={tracker.string.NewProject}
|
||||||
okAction={onSave}
|
okAction={onSave}
|
||||||
canSave={object.label !== ''}
|
canSave={object.label !== ''}
|
||||||
okLabel={plugin.string.CreateProject}
|
okLabel={tracker.string.CreateProject}
|
||||||
spaceClass={plugin.class.Team}
|
spaceClass={tracker.class.Team}
|
||||||
spaceLabel={plugin.string.Team}
|
spaceLabel={tracker.string.Team}
|
||||||
spacePlaceholder={plugin.string.SelectTeam}
|
spacePlaceholder={tracker.string.SelectTeam}
|
||||||
bind:space
|
bind:space
|
||||||
on:close={() => dispatch('close')}
|
on:close={() => dispatch('close')}
|
||||||
>
|
>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<EditBox
|
<EditBox
|
||||||
bind:value={object.label}
|
bind:value={object.label}
|
||||||
placeholder={plugin.string.ProjectNamePlaceholder}
|
placeholder={tracker.string.ProjectNamePlaceholder}
|
||||||
maxWidth="37.5rem"
|
maxWidth="37.5rem"
|
||||||
kind="large-style"
|
kind="large-style"
|
||||||
focus
|
focus
|
||||||
@ -68,7 +69,7 @@
|
|||||||
<div class="description">
|
<div class="description">
|
||||||
<EditBox
|
<EditBox
|
||||||
bind:value={object.description}
|
bind:value={object.description}
|
||||||
placeholder={plugin.string.ProjectDescriptionPlaceholder}
|
placeholder={tracker.string.ProjectDescriptionPlaceholder}
|
||||||
maxWidth="37.5rem"
|
maxWidth="37.5rem"
|
||||||
kind="editbox"
|
kind="editbox"
|
||||||
/>
|
/>
|
||||||
@ -82,20 +83,20 @@
|
|||||||
/>
|
/>
|
||||||
<UserBox
|
<UserBox
|
||||||
_class={contact.class.Employee}
|
_class={contact.class.Employee}
|
||||||
label={plugin.string.ProjectLead}
|
label={tracker.string.ProjectLead}
|
||||||
placeholder={plugin.string.AssignTo}
|
placeholder={tracker.string.AssignTo}
|
||||||
bind:value={object.lead}
|
bind:value={object.lead}
|
||||||
allowDeselect
|
allowDeselect
|
||||||
titleDeselect={plugin.string.Unassigned}
|
titleDeselect={tracker.string.Unassigned}
|
||||||
/>
|
/>
|
||||||
<UserBoxList
|
<UserBoxList
|
||||||
_class={contact.class.Employee}
|
_class={contact.class.Employee}
|
||||||
bind:items={object.members}
|
bind:items={object.members}
|
||||||
label={plugin.string.ProjectStatusPlaceholder}
|
label={tracker.string.ProjectStatusPlaceholder}
|
||||||
/>
|
/>
|
||||||
<!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate -->
|
<!-- TODO: add labels after customize IssueNeedsToBeCompletedByThisDate -->
|
||||||
<DatePresenter bind:value={object.startDate} labelNull={plugin.string.StartDate} editable />
|
<DatePresenter bind:value={object.startDate} labelNull={tracker.string.StartDate} editable />
|
||||||
<DatePresenter bind:value={object.targetDate} labelNull={plugin.string.TargetDate} editable />
|
<DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
import ObjectPresenter from '@anticrm/view-resources/src/components/ObjectPresenter.svelte'
|
import ObjectPresenter from '@anticrm/view-resources/src/components/ObjectPresenter.svelte'
|
||||||
import { Project, ProjectStatus, Team } from '@anticrm/tracker'
|
import { Project, ProjectStatus, Team } from '@anticrm/tracker'
|
||||||
import plugin from '../../plugin'
|
|
||||||
import ProjectStatusSelector from './ProjectStatusSelector.svelte'
|
import ProjectStatusSelector from './ProjectStatusSelector.svelte'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let value: WithLookup<Project>
|
export let value: WithLookup<Project>
|
||||||
export let space: Ref<Team>
|
export let space: Ref<Team>
|
||||||
@ -29,13 +29,13 @@
|
|||||||
const lead = value.$lookup?.lead
|
const lead = value.$lookup?.lead
|
||||||
|
|
||||||
async function updateStatus (status: ProjectStatus) {
|
async function updateStatus (status: ProjectStatus) {
|
||||||
await client.updateDoc(plugin.class.Project, space, value._id, { status })
|
await client.updateDoc(tracker.class.Project, space, value._id, { status })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-presenter">
|
<div class="flex-presenter">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon icon={plugin.icon.Project} size="small" />
|
<Icon icon={value.icon} size="small" />
|
||||||
</div>
|
</div>
|
||||||
<span class="label nowrap project-label">{value.label}</span>
|
<span class="label nowrap project-label">{value.label}</span>
|
||||||
{#if lead}
|
{#if lead}
|
||||||
|
@ -44,7 +44,8 @@
|
|||||||
key: '',
|
key: '',
|
||||||
presenter: plugin.component.ProjectPresenter,
|
presenter: plugin.component.ProjectPresenter,
|
||||||
label: plugin.string.Project,
|
label: plugin.string.Project,
|
||||||
sortingKey: 'name'
|
sortingKey: 'name',
|
||||||
|
props: { space }
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
query={{}}
|
query={{}}
|
||||||
|
@ -30,6 +30,7 @@ import IssuePresenter from './components/issues/IssuePresenter.svelte'
|
|||||||
import TitlePresenter from './components/issues/TitlePresenter.svelte'
|
import TitlePresenter from './components/issues/TitlePresenter.svelte'
|
||||||
import PriorityPresenter from './components/issues/PriorityPresenter.svelte'
|
import PriorityPresenter from './components/issues/PriorityPresenter.svelte'
|
||||||
import PriorityEditor from './components/issues/PriorityEditor.svelte'
|
import PriorityEditor from './components/issues/PriorityEditor.svelte'
|
||||||
|
import ProjectEditor from './components/issues/ProjectEditor.svelte'
|
||||||
import StatusPresenter from './components/issues/StatusPresenter.svelte'
|
import StatusPresenter from './components/issues/StatusPresenter.svelte'
|
||||||
import StatusEditor from './components/issues/StatusEditor.svelte'
|
import StatusEditor from './components/issues/StatusEditor.svelte'
|
||||||
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
|
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
|
||||||
@ -57,6 +58,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
ModificationDatePresenter,
|
ModificationDatePresenter,
|
||||||
PriorityPresenter,
|
PriorityPresenter,
|
||||||
PriorityEditor,
|
PriorityEditor,
|
||||||
|
ProjectEditor,
|
||||||
StatusPresenter,
|
StatusPresenter,
|
||||||
StatusEditor,
|
StatusEditor,
|
||||||
AssigneePresenter,
|
AssigneePresenter,
|
||||||
|
@ -115,6 +115,10 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Filter: '' as IntlString,
|
Filter: '' as IntlString,
|
||||||
ClearFilters: '' as IntlString,
|
ClearFilters: '' as IntlString,
|
||||||
Back: '' as IntlString,
|
Back: '' as IntlString,
|
||||||
|
AssetLabel: '' as IntlString,
|
||||||
|
AddToProject: '' as IntlString,
|
||||||
|
MoveToProject: '' as IntlString,
|
||||||
|
NoProject: '' as IntlString,
|
||||||
|
|
||||||
IssueTitlePlaceholder: '' as IntlString,
|
IssueTitlePlaceholder: '' as IntlString,
|
||||||
IssueDescriptionPlaceholder: '' as IntlString,
|
IssueDescriptionPlaceholder: '' as IntlString,
|
||||||
@ -145,6 +149,7 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
ModificationDatePresenter: '' as AnyComponent,
|
ModificationDatePresenter: '' as AnyComponent,
|
||||||
PriorityPresenter: '' as AnyComponent,
|
PriorityPresenter: '' as AnyComponent,
|
||||||
PriorityEditor: '' as AnyComponent,
|
PriorityEditor: '' as AnyComponent,
|
||||||
|
ProjectEditor: '' as AnyComponent,
|
||||||
StatusPresenter: '' as AnyComponent,
|
StatusPresenter: '' as AnyComponent,
|
||||||
StatusEditor: '' as AnyComponent,
|
StatusEditor: '' as AnyComponent,
|
||||||
AssigneePresenter: '' as AnyComponent,
|
AssigneePresenter: '' as AnyComponent,
|
||||||
|
@ -103,6 +103,7 @@ export interface Issue extends Doc {
|
|||||||
|
|
||||||
number: number
|
number: number
|
||||||
assignee: Ref<Employee> | null
|
assignee: Ref<Employee> | null
|
||||||
|
project: Ref<Project> | null
|
||||||
|
|
||||||
// For subtasks
|
// For subtasks
|
||||||
parentIssue?: Ref<Issue>
|
parentIssue?: Ref<Issue>
|
||||||
@ -149,6 +150,7 @@ export enum ProjectStatus {
|
|||||||
export interface Project extends Doc {
|
export interface Project extends Doc {
|
||||||
label: string
|
label: string
|
||||||
description?: Markup
|
description?: Markup
|
||||||
|
icon: Asset
|
||||||
|
|
||||||
status: ProjectStatus
|
status: ProjectStatus
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user