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