mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-30 12:20:00 +00:00
Tracker: Add project issue list view (#2012)
Signed-off-by: Dvinyanin Alexandr <dvinyanin.alexandr@gmail.com>
This commit is contained in:
parent
dbc25d6ae9
commit
e47d03d87a
@ -11,6 +11,7 @@ Platform:
|
||||
- Fix skills/labels activity
|
||||
- Project selector in issue list
|
||||
- Save last filter for page
|
||||
- Project issue list view
|
||||
|
||||
HR:
|
||||
|
||||
|
@ -212,7 +212,7 @@ export class TProject extends TDoc implements Project {
|
||||
// @Index(IndexKind.FullText)
|
||||
label!: string
|
||||
|
||||
@Prop(TypeMarkup(), tracker.string.Project)
|
||||
@Prop(TypeMarkup(), tracker.string.Description)
|
||||
description?: Markup
|
||||
|
||||
@Prop(TypeString(), tracker.string.AssetLabel)
|
||||
@ -236,10 +236,10 @@ export class TProject extends TDoc implements Project {
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, undefined, attachment.string.Files)
|
||||
attachments?: number
|
||||
|
||||
@Prop(TypeDate(true), tracker.string.Project)
|
||||
@Prop(TypeDate(true), tracker.string.StartDate)
|
||||
startDate!: Timestamp | null
|
||||
|
||||
@Prop(TypeDate(true), tracker.string.Project)
|
||||
@Prop(TypeDate(true), tracker.string.TargetDate)
|
||||
targetDate!: Timestamp | null
|
||||
|
||||
declare space: Ref<Team>
|
||||
@ -364,6 +364,12 @@ export function createModel (builder: Builder): void {
|
||||
label: tracker.string.Views,
|
||||
icon: tracker.icon.Views,
|
||||
component: tracker.component.Views
|
||||
},
|
||||
{
|
||||
id: 'project',
|
||||
label: tracker.string.Project,
|
||||
component: tracker.component.EditProject,
|
||||
visibleIf: tracker.function.ProjectVisible
|
||||
}
|
||||
],
|
||||
spaces: [
|
||||
|
@ -58,7 +58,7 @@
|
||||
"CategoryCanceled": "Canceled",
|
||||
|
||||
"Title": "Title",
|
||||
"Description": "",
|
||||
"Description": "Description",
|
||||
"Status": "Status",
|
||||
"Number": "Number",
|
||||
"Assignee": "Assignee",
|
||||
|
@ -49,7 +49,7 @@
|
||||
"AddSubIssues": "{subIssues, plural, =1 {Добавить подзадачу} other {+ Добавить подзадачи}}",
|
||||
|
||||
"Title": "Заголовок",
|
||||
"Description": "",
|
||||
"Description": "Описание",
|
||||
"Status": "Статус",
|
||||
"Number": "Number",
|
||||
"Assignee": "Исполнитель",
|
||||
|
@ -65,6 +65,7 @@
|
||||
export let completedIssuesPeriod: IssuesDateModificationPeriod | null = IssuesDateModificationPeriod.All
|
||||
export let shouldShowEmptyGroups: boolean | undefined = false
|
||||
export let includedGroups: Partial<Record<IssuesGroupByKeys, Array<any>>> = {}
|
||||
export let label: string | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const ENTRIES_LIMIT = 200
|
||||
@ -439,18 +440,16 @@
|
||||
filterElement
|
||||
)
|
||||
}
|
||||
$: value = totalIssuesCount === resultIssuesCount ? totalIssuesCount : `${resultIssuesCount}/${totalIssuesCount}`
|
||||
</script>
|
||||
|
||||
{#if currentTeam}
|
||||
<div class="fs-title flex-between header">
|
||||
<div class="titleContainer">
|
||||
{#if totalIssuesCount === resultIssuesCount}
|
||||
<Label label={title} params={{ value: totalIssuesCount }} />
|
||||
{#if label}
|
||||
{label}
|
||||
{:else}
|
||||
<div class="labelsContainer">
|
||||
<Label label={title} params={{ value: resultIssuesCount }} />
|
||||
<div class="totalIssuesLabel">/{totalIssuesCount}</div>
|
||||
</div>
|
||||
<Label label={title} params={{ value }} />
|
||||
{/if}
|
||||
<div class="ml-4">
|
||||
<Button
|
||||
@ -486,36 +485,46 @@
|
||||
onChangeMode={handleFilterModeChanged}
|
||||
/>
|
||||
{/if}
|
||||
<ScrollBox vertical stretch>
|
||||
<IssuesListBrowser
|
||||
_class={tracker.class.Issue}
|
||||
{currentSpace}
|
||||
{groupByKey}
|
||||
orderBy={issuesOrderKeyMap[orderingKey]}
|
||||
{statuses}
|
||||
{employees}
|
||||
categories={displayedCategories}
|
||||
itemsConfig={[
|
||||
{ key: '', presenter: tracker.component.PriorityEditor },
|
||||
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
|
||||
{ key: '', presenter: tracker.component.StatusEditor, props: { statuses } },
|
||||
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true } },
|
||||
{ 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',
|
||||
presenter: tracker.component.AssigneePresenter,
|
||||
props: { currentSpace, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||
}
|
||||
]}
|
||||
{groupedIssues}
|
||||
/>
|
||||
</ScrollBox>
|
||||
|
||||
<div class="flex h-full">
|
||||
<div class="antiPanel-component">
|
||||
<ScrollBox vertical stretch>
|
||||
<IssuesListBrowser
|
||||
_class={tracker.class.Issue}
|
||||
{currentSpace}
|
||||
{groupByKey}
|
||||
orderBy={issuesOrderKeyMap[orderingKey]}
|
||||
{statuses}
|
||||
{employees}
|
||||
categories={displayedCategories}
|
||||
itemsConfig={[
|
||||
{ key: '', presenter: tracker.component.PriorityEditor },
|
||||
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
|
||||
{ key: '', presenter: tracker.component.StatusEditor, props: { statuses } },
|
||||
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true } },
|
||||
{ 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',
|
||||
presenter: tracker.component.AssigneePresenter,
|
||||
props: { currentSpace, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||
}
|
||||
]}
|
||||
{groupedIssues}
|
||||
/>
|
||||
</ScrollBox>
|
||||
</div>
|
||||
{#if $$slots.aside !== undefined}
|
||||
<div class="antiPanel-component aside border-left">
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@ -530,13 +539,4 @@
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.labelsContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.totalIssuesLabel {
|
||||
color: var(--content-color);
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { Project } from '@anticrm/tracker'
|
||||
import { EditBox, getCurrentLocation } from '@anticrm/ui'
|
||||
import { DocAttributeBar } from '@anticrm/view-resources'
|
||||
import Issues from '../issues/Issues.svelte'
|
||||
|
||||
import tracker from '../../plugin'
|
||||
|
||||
let object: Project | undefined
|
||||
$: project = getCurrentLocation().path[3] as Ref<Project>
|
||||
|
||||
const client = getClient()
|
||||
const projectQuery = createQuery()
|
||||
$: projectQuery.query(tracker.class.Project, { _id: project }, (result) => {
|
||||
;[object] = result
|
||||
})
|
||||
|
||||
async function change (field: string, value: any) {
|
||||
if (object) {
|
||||
await client.update(object, { [field]: value })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if object}
|
||||
<Issues currentSpace={object.space} query={{ project }} label={object.label}>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="flex-row p-4">
|
||||
<div class="fs-title text-xl">
|
||||
<EditBox bind:value={object.label} maxWidth="39rem" on:change={() => change('label', object?.label)} />
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<StyledTextBox
|
||||
alwaysEdit={true}
|
||||
showButtons={false}
|
||||
placeholder={tracker.string.Description}
|
||||
content={object.description ?? ''}
|
||||
on:value={(evt) => change('description', evt.detail)}
|
||||
/>
|
||||
</div>
|
||||
<DocAttributeBar {object} mixins={[]} ignoreKeys={['icon', 'label', 'description']} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Issues>
|
||||
{/if}
|
@ -15,12 +15,20 @@
|
||||
<script lang="ts">
|
||||
import { WithLookup } from '@anticrm/core'
|
||||
import { Project } from '@anticrm/tracker'
|
||||
import { getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
|
||||
export let value: WithLookup<Project>
|
||||
function navigateToProject () {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = 'project'
|
||||
loc.path[3] = value._id
|
||||
loc.path.length = 4
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div class="flex-presenter projectPresenterRoot">
|
||||
<div class="flex-presenter projectPresenterRoot" on:click={navigateToProject}>
|
||||
<span title={value.label} class="projectLabel">{value.label}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -44,6 +44,7 @@ import LeadPresenter from './components/projects/LeadPresenter.svelte'
|
||||
import TargetDatePresenter from './components/projects/TargetDatePresenter.svelte'
|
||||
import ProjectMembersPresenter from './components/projects/ProjectMembersPresenter.svelte'
|
||||
import ProjectStatusPresenter from './components/projects/ProjectStatusPresenter.svelte'
|
||||
import EditProject from './components/projects/EditProject.svelte'
|
||||
|
||||
import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte'
|
||||
import EditIssue from './components/issues/edit/EditIssue.svelte'
|
||||
@ -81,6 +82,10 @@ export default async (): Promise<Resources> => ({
|
||||
ProjectMembersPresenter,
|
||||
ProjectStatusPresenter,
|
||||
SetDueDateActionPopup,
|
||||
SetParentIssueActionPopup
|
||||
SetParentIssueActionPopup,
|
||||
EditProject
|
||||
},
|
||||
function: {
|
||||
ProjectVisible: () => false
|
||||
}
|
||||
})
|
||||
|
@ -12,10 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import type { IntlString, Resource } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import tracker, { trackerId } from '../../tracker/lib'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { Space } from '@anticrm/core'
|
||||
|
||||
export default mergeIds(trackerId, tracker, {
|
||||
string: {
|
||||
@ -187,6 +188,10 @@ export default mergeIds(trackerId, tracker, {
|
||||
ProjectMembersPresenter: '' as AnyComponent,
|
||||
ProjectStatusPresenter: '' as AnyComponent,
|
||||
SetDueDateActionPopup: '' as AnyComponent,
|
||||
SetParentIssueActionPopup: '' as AnyComponent
|
||||
SetParentIssueActionPopup: '' as AnyComponent,
|
||||
EditProject: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
ProjectVisible: '' as '' as Resource<(spaces: Space[]) => boolean>
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user