mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-23 16:56:07 +00:00
Fix TSK-101 navigation (#2068)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
5d33062176
commit
665ef78199
@ -17,13 +17,9 @@
|
|||||||
import { createQuery, getClient } from '@anticrm/presentation'
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
import { getPlatformColor, ScrollBox, Scroller } from '@anticrm/ui'
|
import { getPlatformColor, ScrollBox, Scroller } from '@anticrm/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { slide } from 'svelte/transition'
|
import { CardDragEvent, ExtItem, Item, StateType, TypeState } from '../types'
|
||||||
import { DocWithRank, StateType, TypeState } from '../types'
|
|
||||||
import { calcRank } from '../utils'
|
import { calcRank } from '../utils'
|
||||||
|
import KanbanRow from './KanbanRow.svelte'
|
||||||
type Item = DocWithRank & { state: StateType; doneState: StateType | null }
|
|
||||||
type ExtItem = { prev?: Item; it: Item; next?: Item; pos: number }
|
|
||||||
type CardDragEvent = DragEvent & { currentTarget: EventTarget & HTMLDivElement }
|
|
||||||
|
|
||||||
export let _class: Ref<Class<Item>>
|
export let _class: Ref<Class<Item>>
|
||||||
export let space: Ref<Space>
|
export let space: Ref<Space>
|
||||||
@ -144,7 +140,6 @@
|
|||||||
return calcRank(object.it, object.next)
|
return calcRank(object.it, object.next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const slideD = (node: any, args: any) => (args.isDragging ? slide(node, args) : {})
|
|
||||||
|
|
||||||
function panelDragOver (event: Event, state: TypeState): void {
|
function panelDragOver (event: Event, state: TypeState): void {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@ -184,11 +179,14 @@
|
|||||||
let stateObjects: ExtItem[]
|
let stateObjects: ExtItem[]
|
||||||
|
|
||||||
const stateRefs: HTMLElement[] = []
|
const stateRefs: HTMLElement[] = []
|
||||||
|
const stateRows: KanbanRow[] = []
|
||||||
|
|
||||||
$: stateRefs.length = states.length
|
$: stateRefs.length = states.length
|
||||||
|
$: stateRows.length = states.length
|
||||||
|
|
||||||
function scrollInto (statePos: number): void {
|
function scrollInto (statePos: number, obj: Item): void {
|
||||||
stateRefs[statePos]?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
|
stateRefs[statePos]?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
|
||||||
|
stateRows[statePos]?.scroll(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function select (offset: 1 | -1 | 0, of?: Doc, dir?: 'vertical' | 'horizontal'): void {
|
export function select (offset: 1 | -1 | 0, of?: Doc, dir?: 'vertical' | 'horizontal'): void {
|
||||||
@ -227,16 +225,18 @@
|
|||||||
|
|
||||||
if (offset === -1) {
|
if (offset === -1) {
|
||||||
if (dir === undefined || dir === 'vertical') {
|
if (dir === undefined || dir === 'vertical') {
|
||||||
scrollInto(objState)
|
const obj = (stateObjs[statePos - 1] ?? stateObjs[0]).it
|
||||||
dispatch('obj-focus', (stateObjs[statePos - 1] ?? stateObjs[0]).it)
|
scrollInto(objState, obj)
|
||||||
|
dispatch('obj-focus', obj)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
while (objState > 0) {
|
while (objState > 0) {
|
||||||
objState--
|
objState--
|
||||||
const nstateObjs = getStateObjects(objects, states[objState])
|
const nstateObjs = getStateObjects(objects, states[objState])
|
||||||
if (nstateObjs.length > 0) {
|
if (nstateObjs.length > 0) {
|
||||||
scrollInto(objState)
|
const obj = (nstateObjs[statePos] ?? nstateObjs[nstateObjs.length - 1]).it
|
||||||
dispatch('obj-focus', (nstateObjs[statePos] ?? nstateObjs[nstateObjs.length - 1]).it)
|
scrollInto(objState, obj)
|
||||||
|
dispatch('obj-focus', obj)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,23 +244,25 @@
|
|||||||
}
|
}
|
||||||
if (offset === 1) {
|
if (offset === 1) {
|
||||||
if (dir === undefined || dir === 'vertical') {
|
if (dir === undefined || dir === 'vertical') {
|
||||||
scrollInto(objState)
|
const obj = (stateObjs[statePos + 1] ?? stateObjs[stateObjs.length - 1]).it
|
||||||
dispatch('obj-focus', (stateObjs[statePos + 1] ?? stateObjs[stateObjs.length - 1]).it)
|
scrollInto(objState, obj)
|
||||||
|
dispatch('obj-focus', obj)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
while (objState < states.length - 1) {
|
while (objState < states.length - 1) {
|
||||||
objState++
|
objState++
|
||||||
const nstateObjs = getStateObjects(objects, states[objState])
|
const nstateObjs = getStateObjects(objects, states[objState])
|
||||||
if (nstateObjs.length > 0) {
|
if (nstateObjs.length > 0) {
|
||||||
scrollInto(objState)
|
const obj = (nstateObjs[statePos] ?? nstateObjs[nstateObjs.length - 1]).it
|
||||||
dispatch('obj-focus', (nstateObjs[statePos] ?? nstateObjs[nstateObjs.length - 1]).it)
|
scrollInto(objState, obj)
|
||||||
|
dispatch('obj-focus', obj)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (offset === 0) {
|
if (offset === 0) {
|
||||||
scrollInto(objState)
|
scrollInto(objState, obj)
|
||||||
dispatch('obj-focus', obj)
|
dispatch('obj-focus', obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,35 +311,25 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<Scroller padding={'.5rem 0'} on:dragover on:drop>
|
<Scroller padding={'.5rem 0'} on:dragover on:drop>
|
||||||
<slot name="beforeCard" {state} />
|
<slot name="beforeCard" {state} />
|
||||||
{#each stateObjects as object}
|
<KanbanRow
|
||||||
{@const dragged = isDragging && object.it._id === dragCard?._id}
|
bind:this={stateRows[si]}
|
||||||
<div
|
{stateObjects}
|
||||||
transition:slideD|local={{ isDragging }}
|
{isDragging}
|
||||||
class="step-tb75"
|
{dragCard}
|
||||||
on:dragover|preventDefault={(evt) => cardDragOver(evt, object)}
|
{objects}
|
||||||
on:drop|preventDefault={(evt) => cardDrop(evt, object)}
|
{selection}
|
||||||
>
|
{checkedSet}
|
||||||
<div
|
{state}
|
||||||
class="card-container"
|
{cardDragOver}
|
||||||
class:selection={selection !== undefined ? objects[selection]?._id === object.it._id : false}
|
{cardDrop}
|
||||||
class:checked={checkedSet.has(object.it._id)}
|
{onDragStart}
|
||||||
on:mouseover={() => dispatch('obj-focus', object.it)}
|
{showMenu}
|
||||||
on:focus={() => {}}
|
>
|
||||||
on:contextmenu={(evt) => showMenu(evt, object)}
|
<svelte:fragment slot="card" let:object let:dragged>
|
||||||
draggable={true}
|
<slot name="card" {object} {dragged} />
|
||||||
class:draggable={true}
|
</svelte:fragment>
|
||||||
on:dragstart
|
</KanbanRow>
|
||||||
on:dragend
|
|
||||||
class:dragged
|
|
||||||
on:dragstart={() => onDragStart(object, state)}
|
|
||||||
on:dragend={() => {
|
|
||||||
isDragging = false
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<slot name="card" object={toAny(object.it)} {dragged} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<slot name="afterCard" {space} {state} />
|
<slot name="afterCard" {space} {state} />
|
||||||
</Scroller>
|
</Scroller>
|
||||||
</div>
|
</div>
|
||||||
@ -361,42 +353,6 @@
|
|||||||
padding: 1.5rem 2rem 0;
|
padding: 1.5rem 2rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-container {
|
|
||||||
background-color: var(--board-card-bg-color);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
// transition: box-shadow .15s ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--board-card-bg-hover);
|
|
||||||
}
|
|
||||||
&.checked {
|
|
||||||
background-color: var(--highlight-select);
|
|
||||||
box-shadow: inset 0 0 1px 1px var(--highlight-select-border);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--highlight-select-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.selection,
|
|
||||||
&.checked.selection {
|
|
||||||
box-shadow: inset 0 0 1px 1px var(--primary-bg-color);
|
|
||||||
animation: anim-border 1s ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--highlight-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.checked.selection:hover {
|
|
||||||
background-color: var(--highlight-select-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.draggable {
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
&.dragged {
|
|
||||||
background-color: var(--board-bg-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes anim-border {
|
@keyframes anim-border {
|
||||||
from {
|
from {
|
||||||
box-shadow: inset 0 0 1px 1px var(--primary-edit-border-color);
|
box-shadow: inset 0 0 1px 1px var(--primary-edit-border-color);
|
||||||
|
129
packages/kanban/src/components/KanbanRow.svelte
Normal file
129
packages/kanban/src/components/KanbanRow.svelte
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Doc, Ref } from '@anticrm/core'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { slide } from 'svelte/transition'
|
||||||
|
import { CardDragEvent, ExtItem, Item, TypeState } from '../types'
|
||||||
|
|
||||||
|
export let stateObjects: ExtItem[]
|
||||||
|
export let isDragging: boolean
|
||||||
|
export let dragCard: Item | undefined
|
||||||
|
export let objects: Item[]
|
||||||
|
export let selection: number | undefined = undefined
|
||||||
|
export let checkedSet: Set<Ref<Doc>>
|
||||||
|
export let state: TypeState
|
||||||
|
|
||||||
|
export let cardDragOver: (evt: CardDragEvent, object: ExtItem) => void
|
||||||
|
export let cardDrop: (evt: CardDragEvent, object: ExtItem) => void
|
||||||
|
export let onDragStart: (object: ExtItem, state: TypeState) => void
|
||||||
|
export let showMenu: (evt: MouseEvent, object: ExtItem) => void
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
function toAny (object: any): any {
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
const slideD = (node: any, args: any) => (args.isDragging ? slide(node, args) : {})
|
||||||
|
|
||||||
|
const stateRefs: HTMLElement[] = []
|
||||||
|
|
||||||
|
$: stateRefs.length = stateObjects.length
|
||||||
|
export function scroll (item: Item): void {
|
||||||
|
const pos = stateObjects.findIndex((it) => it.it._id === item._id)
|
||||||
|
if (pos >= 0) {
|
||||||
|
stateRefs[pos]?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each stateObjects as object, i}
|
||||||
|
{@const dragged = isDragging && object.it._id === dragCard?._id}
|
||||||
|
<div
|
||||||
|
bind:this={stateRefs[i]}
|
||||||
|
transition:slideD|local={{ isDragging }}
|
||||||
|
class="step-tb75"
|
||||||
|
on:dragover|preventDefault={(evt) => cardDragOver(evt, object)}
|
||||||
|
on:drop|preventDefault={(evt) => cardDrop(evt, object)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card-container"
|
||||||
|
class:selection={selection !== undefined ? objects[selection]?._id === object.it._id : false}
|
||||||
|
class:checked={checkedSet.has(object.it._id)}
|
||||||
|
on:mouseover={() => dispatch('obj-focus', object.it)}
|
||||||
|
on:focus={() => {}}
|
||||||
|
on:contextmenu={(evt) => showMenu(evt, object)}
|
||||||
|
draggable={true}
|
||||||
|
class:draggable={true}
|
||||||
|
on:dragstart
|
||||||
|
on:dragend
|
||||||
|
class:dragged
|
||||||
|
on:dragstart={() => onDragStart(object, state)}
|
||||||
|
on:dragend={() => {
|
||||||
|
isDragging = false
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<slot name="card" object={toAny(object.it)} {dragged} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.card-container {
|
||||||
|
background-color: var(--board-card-bg-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
// transition: box-shadow .15s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--board-card-bg-hover);
|
||||||
|
}
|
||||||
|
&.checked {
|
||||||
|
background-color: var(--highlight-select);
|
||||||
|
box-shadow: inset 0 0 1px 1px var(--highlight-select-border);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--highlight-select-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.selection,
|
||||||
|
&.checked.selection {
|
||||||
|
box-shadow: inset 0 0 1px 1px var(--primary-bg-color);
|
||||||
|
animation: anim-border 1s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--highlight-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.checked.selection:hover {
|
||||||
|
background-color: var(--highlight-select-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.draggable {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
&.dragged {
|
||||||
|
background-color: var(--board-bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes anim-border {
|
||||||
|
from {
|
||||||
|
box-shadow: inset 0 0 1px 1px var(--primary-edit-border-color);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
box-shadow: inset 0 0 1px 1px var(--primary-bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -19,3 +19,20 @@ export interface TypeState {
|
|||||||
color: number
|
color: number
|
||||||
icon?: Asset
|
icon?: Asset
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type Item = DocWithRank & { state: StateType, doneState: StateType | null }
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface ExtItem {
|
||||||
|
prev?: Item
|
||||||
|
it: Item
|
||||||
|
next?: Item
|
||||||
|
pos: number
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type CardDragEvent = DragEvent & { currentTarget: EventTarget & HTMLDivElement }
|
||||||
|
@ -45,7 +45,6 @@
|
|||||||
<Avatar size={'x-small'} avatar={employee.avatar} />
|
<Avatar size={'x-small'} avatar={employee.avatar} />
|
||||||
<div class="overflow-label user">{formatName(employee.name)}</div>
|
<div class="overflow-label user">{formatName(employee.name)}</div>
|
||||||
{:else}
|
{:else}
|
||||||
{JSON.stringify(value)}
|
|
||||||
<div class="overflow-label user">{value.email}</div>
|
<div class="overflow-label user">{value.email}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, SortingOrder } from '@anticrm/core'
|
import { Ref, SortingOrder } from '@anticrm/core'
|
||||||
import { Project } from '@anticrm/tracker'
|
|
||||||
import { IntlString, translate } from '@anticrm/platform'
|
import { IntlString, translate } from '@anticrm/platform'
|
||||||
import { createQuery, getClient } from '@anticrm/presentation'
|
import { getClient } from '@anticrm/presentation'
|
||||||
import { Button, showPopup, SelectPopup, eventToHTMLElement, ButtonShape } from '@anticrm/ui'
|
import { Project } from '@anticrm/tracker'
|
||||||
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
||||||
|
import { Button, ButtonShape, eventToHTMLElement, SelectPopup, showPopup } from '@anticrm/ui'
|
||||||
import tracker from '../plugin'
|
import tracker from '../plugin'
|
||||||
|
|
||||||
export let value: Ref<Project> | null | undefined
|
export let value: Ref<Project> | null | undefined
|
||||||
@ -33,23 +33,10 @@
|
|||||||
export let width: string | undefined = 'min-content'
|
export let width: string | undefined = 'min-content'
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const projectsQuery = createQuery()
|
|
||||||
|
|
||||||
let projects: Project[] = []
|
|
||||||
let selectedProject: Project | undefined
|
let selectedProject: Project | undefined
|
||||||
let defaultProjectLabel = ''
|
let defaultProjectLabel = ''
|
||||||
|
|
||||||
$: projectsQuery.query(
|
|
||||||
tracker.class.Project,
|
|
||||||
{},
|
|
||||||
(currentProjects) => {
|
|
||||||
projects = currentProjects
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sort: { modifiedOn: SortingOrder.Ascending }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
$: if (value !== undefined) {
|
$: if (value !== undefined) {
|
||||||
handleSelectedProjectIdUpdated(value)
|
handleSelectedProjectIdUpdated(value)
|
||||||
}
|
}
|
||||||
@ -58,15 +45,6 @@
|
|||||||
$: projectIcon = selectedProject?.icon ?? tracker.icon.Projects
|
$: projectIcon = selectedProject?.icon ?? tracker.icon.Projects
|
||||||
$: projectText = shouldShowLabel ? selectedProject?.label ?? defaultProjectLabel : undefined
|
$: 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) => {
|
const handleSelectedProjectIdUpdated = async (newProjectId: Ref<Project> | null) => {
|
||||||
if (newProjectId === null) {
|
if (newProjectId === null) {
|
||||||
selectedProject = undefined
|
selectedProject = undefined
|
||||||
@ -77,12 +55,28 @@
|
|||||||
selectedProject = await client.findOne(tracker.class.Project, { _id: newProjectId })
|
selectedProject = await client.findOne(tracker.class.Project, { _id: newProjectId })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleProjectEditorOpened = (event: MouseEvent) => {
|
const handleProjectEditorOpened = async (event: MouseEvent): Promise<void> => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
if (!isEditable) {
|
if (!isEditable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const projects = await client.findAll(
|
||||||
|
tracker.class.Project,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
sort: { modifiedOn: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const projectsInfo = [
|
||||||
|
{ id: null, icon: tracker.icon.Projects, label: tracker.string.NoProject },
|
||||||
|
...projects.map((p) => ({
|
||||||
|
id: p._id,
|
||||||
|
icon: p.icon,
|
||||||
|
text: p.label
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
|
||||||
showPopup(
|
showPopup(
|
||||||
SelectPopup,
|
SelectPopup,
|
||||||
{ value: projectsInfo, placeholder: popupPlaceholder, searchable: true },
|
{ value: projectsInfo, placeholder: popupPlaceholder, searchable: true },
|
||||||
|
@ -69,7 +69,11 @@
|
|||||||
|
|
||||||
const options: FindOptions<Issue> = {
|
const options: FindOptions<Issue> = {
|
||||||
lookup: {
|
lookup: {
|
||||||
assignee: contact.class.Employee
|
assignee: contact.class.Employee,
|
||||||
|
space: tracker.class.Team,
|
||||||
|
_id: {
|
||||||
|
subIssues: tracker.class.Issue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +161,7 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex-col mr-6">
|
<div class="flex-col mr-6">
|
||||||
<IssuePresenter value={object} {currentTeam} />
|
<IssuePresenter value={object} />
|
||||||
<span class="fs-bold caption-color mt-1 lines-limit-2">
|
<span class="fs-bold caption-color mt-1 lines-limit-2">
|
||||||
{object.title}
|
{object.title}
|
||||||
</span>
|
</span>
|
||||||
|
@ -13,40 +13,29 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createQuery, getClient } from '@anticrm/presentation'
|
import { WithLookup } from '@anticrm/core'
|
||||||
import type { Issue, Team } from '@anticrm/tracker'
|
import type { Issue, Team } from '@anticrm/tracker'
|
||||||
import { Icon, showPanel } from '@anticrm/ui'
|
import { Icon, showPanel } from '@anticrm/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { getIssueId } from '../../utils'
|
|
||||||
|
|
||||||
export let value: Issue
|
export let value: WithLookup<Issue>
|
||||||
export let currentTeam: Team | undefined
|
|
||||||
export let inline: boolean = false
|
export let inline: boolean = false
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
const spaceQuery = createQuery()
|
|
||||||
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
|
|
||||||
|
|
||||||
function handleIssueEditorOpened () {
|
function handleIssueEditorOpened () {
|
||||||
showPanel(tracker.component.EditIssue, value._id, value._class, 'content')
|
showPanel(tracker.component.EditIssue, value._id, value._class, 'content')
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (!currentTeam) {
|
$: title = `${(value?.$lookup?.space as Team)?.identifier}-${value.number}`
|
||||||
spaceQuery.query(tracker.class.Team, { _id: value.space }, (res) => ([currentTeam] = res))
|
|
||||||
} else {
|
|
||||||
spaceQuery.unsubscribe()
|
|
||||||
}
|
|
||||||
$: issueName = currentTeam && getIssueId(currentTeam, value)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value && shortLabel}
|
{#if value}
|
||||||
<div class="flex-presenter issuePresenterRoot" class:inline-presenter={inline} on:click={handleIssueEditorOpened}>
|
<div class="flex-presenter issuePresenterRoot" class:inline-presenter={inline} on:click={handleIssueEditorOpened}>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon icon={tracker.icon.Issue} size={'small'} />
|
<Icon icon={tracker.icon.Issue} size={'small'} />
|
||||||
</div>
|
</div>
|
||||||
{#if issueName !== undefined}
|
<span title="title" class="label nowrap issueLabel">
|
||||||
<span title={issueName} class="label nowrap issueLabel">{issueName}</span>
|
{title}
|
||||||
{/if}
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -91,7 +91,11 @@
|
|||||||
const options: FindOptions<Issue> = {
|
const options: FindOptions<Issue> = {
|
||||||
sort: { [issuesOrderKeyMap[orderingKey]]: issuesSortOrderMap[issuesOrderKeyMap[orderingKey]] },
|
sort: { [issuesOrderKeyMap[orderingKey]]: issuesSortOrderMap[issuesOrderKeyMap[orderingKey]] },
|
||||||
limit: ENTRIES_LIMIT,
|
limit: ENTRIES_LIMIT,
|
||||||
lookup: { assignee: contact.class.Employee, status: tracker.class.IssueStatus }
|
lookup: {
|
||||||
|
assignee: contact.class.Employee,
|
||||||
|
status: tracker.class.IssueStatus,
|
||||||
|
space: tracker.class.Team
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: baseQuery = {
|
$: baseQuery = {
|
||||||
|
@ -57,7 +57,8 @@
|
|||||||
const baseOptions: FindOptions<Issue> = {
|
const baseOptions: FindOptions<Issue> = {
|
||||||
lookup: {
|
lookup: {
|
||||||
assignee: contact.class.Employee,
|
assignee: contact.class.Employee,
|
||||||
status: tracker.class.IssueStatus
|
status: tracker.class.IssueStatus,
|
||||||
|
space: tracker.class.Team
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,11 @@
|
|||||||
{
|
{
|
||||||
sort: { [orderByKey]: issuesSortOrderMap[orderByKey] },
|
sort: { [orderByKey]: issuesSortOrderMap[orderByKey] },
|
||||||
limit: 200,
|
limit: 200,
|
||||||
lookup: { assignee: contact.class.Employee, status: tracker.class.IssueStatus }
|
lookup: {
|
||||||
|
assignee: contact.class.Employee,
|
||||||
|
status: tracker.class.IssueStatus,
|
||||||
|
space: tracker.class.Team
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
@ -13,15 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SortingOrder, WithLookup, Ref, Doc } from '@anticrm/core'
|
import { Doc, Ref, WithLookup } from '@anticrm/core'
|
||||||
import { createQuery } from '@anticrm/presentation'
|
|
||||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||||
import { Button, ProgressCircle, showPopup, SelectPopup, closeTooltip, showPanel } from '@anticrm/ui'
|
|
||||||
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
||||||
|
import { Button, closeTooltip, ProgressCircle, SelectPopup, showPanel, showPopup } from '@anticrm/ui'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import { getIssueId } from '../../../utils'
|
import { getIssueId } from '../../../utils'
|
||||||
|
|
||||||
export let issue: Issue
|
export let issue: WithLookup<Issue>
|
||||||
export let currentTeam: Team | undefined
|
export let currentTeam: Team | undefined
|
||||||
export let issueStatuses: WithLookup<IssueStatus>[] | undefined
|
export let issueStatuses: WithLookup<IssueStatus>[] | undefined
|
||||||
|
|
||||||
@ -30,7 +29,6 @@
|
|||||||
export let justify: 'left' | 'center' = 'left'
|
export let justify: 'left' | 'center' = 'left'
|
||||||
export let width: string | undefined = 'min-contet'
|
export let width: string | undefined = 'min-contet'
|
||||||
|
|
||||||
const subIssuesQuery = createQuery()
|
|
||||||
let btn: HTMLElement
|
let btn: HTMLElement
|
||||||
|
|
||||||
let subIssues: Issue[] | undefined
|
let subIssues: Issue[] | undefined
|
||||||
@ -38,9 +36,10 @@
|
|||||||
let countComplate: number = 0
|
let countComplate: number = 0
|
||||||
|
|
||||||
$: hasSubIssues = issue.subIssues > 0
|
$: hasSubIssues = issue.subIssues > 0
|
||||||
$: subIssuesQuery.query(tracker.class.Issue, { attachedTo: issue._id }, async (result) => (subIssues = result), {
|
$: if (issue.$lookup?.subIssues !== undefined) {
|
||||||
sort: { rank: SortingOrder.Ascending }
|
subIssues = issue.$lookup.subIssues as Issue[]
|
||||||
})
|
subIssues.sort((a, b) => a.rank.localeCompare(b.rank))
|
||||||
|
}
|
||||||
$: if (issueStatuses && subIssues) {
|
$: if (issueStatuses && subIssues) {
|
||||||
doneStatus = issueStatuses.find((s) => s.category === tracker.issueStatusCategory.Completed)?._id ?? undefined
|
doneStatus = issueStatuses.find((s) => s.category === tracker.issueStatusCategory.Completed)?._id ?? undefined
|
||||||
if (doneStatus) countComplate = subIssues.filter((si) => si.status === doneStatus).length
|
if (doneStatus) countComplate = subIssues.filter((si) => si.status === doneStatus).length
|
||||||
|
@ -54,8 +54,6 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{projectId}
|
|
||||||
{JSON.stringify(project)}
|
|
||||||
{#if project}
|
{#if project}
|
||||||
<EditProject {project} />
|
<EditProject {project} />
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -69,13 +69,12 @@ test.describe('recruit tests', () => {
|
|||||||
await page.click('button:has-text("Vacancy")')
|
await page.click('button:has-text("Vacancy")')
|
||||||
await page.fill('[placeholder="Software\\ Engineer"]', vacancyId)
|
await page.fill('[placeholder="Software\\ Engineer"]', vacancyId)
|
||||||
await page.click('button:has-text("Create")')
|
await page.click('button:has-text("Create")')
|
||||||
await page.locator(`text=${vacancyId}`).click()
|
await page.click(`tr > :has-text("${vacancyId}")`)
|
||||||
|
|
||||||
await page.click('text=Talents')
|
await page.click('text=Talents')
|
||||||
|
|
||||||
await page.click('text=Talents')
|
await page.click('text=Talents')
|
||||||
await page.click('text=Andrey P.')
|
await page.click('text=Andrey P.')
|
||||||
// await page.locator('.mixin-selector').locator('text="Candidate"').click()
|
|
||||||
|
|
||||||
// Click on Add button
|
// Click on Add button
|
||||||
await page.click('.applications-container .flex-row-center .flex-center')
|
await page.click('.applications-container .flex-row-center .flex-center')
|
||||||
@ -87,7 +86,7 @@ test.describe('recruit tests', () => {
|
|||||||
|
|
||||||
await page.click('button:has-text("Create")')
|
await page.click('button:has-text("Create")')
|
||||||
|
|
||||||
await page.locator(`tr:has-text("${vacancyId}") >> text=APP-`).click()
|
await page.click(`tr:has-text("${vacancyId}") >> text=APP-`)
|
||||||
await page.click('button:has-text("Assigned recruiter")')
|
await page.click('button:has-text("Assigned recruiter")')
|
||||||
await page.click('button:has-text("Rosamund Chen")')
|
await page.click('button:has-text("Rosamund Chen")')
|
||||||
})
|
})
|
||||||
@ -100,9 +99,9 @@ test.describe('recruit tests', () => {
|
|||||||
await page.locator('text=Vacancies').click()
|
await page.locator('text=Vacancies').click()
|
||||||
|
|
||||||
await page.click('button:has-text("Vacancy")')
|
await page.click('button:has-text("Vacancy")')
|
||||||
await page.fill('[placeholder="Software\\ Engineer"]', vacancyId)
|
await page.fill('form [placeholder="Software\\ Engineer"]', vacancyId)
|
||||||
await page.click('button:has-text("Create")')
|
await page.click('form button:has-text("Create")')
|
||||||
await page.locator(`text=${vacancyId}`).click()
|
await page.click(`tr > :has-text("${vacancyId}")`)
|
||||||
|
|
||||||
// Create Applicatio n1
|
// Create Applicatio n1
|
||||||
await page.click('button:has-text("Application")')
|
await page.click('button:has-text("Application")')
|
||||||
|
@ -90,17 +90,17 @@ test.describe('recruit tests', () => {
|
|||||||
// Click [placeholder="John"]
|
// Click [placeholder="John"]
|
||||||
await page.click('[placeholder="John"]')
|
await page.click('[placeholder="John"]')
|
||||||
// Fill [placeholder="John"]
|
// Fill [placeholder="John"]
|
||||||
const first = 'first-' + generateId().slice(0, 4)
|
const first = 'first-' + generateId(4)
|
||||||
await page.fill('[placeholder="John"]', first)
|
await page.fill('[placeholder="John"]', first)
|
||||||
// Click [placeholder="Appleseed"]
|
// Click [placeholder="Appleseed"]
|
||||||
await page.click('[placeholder="Appleseed"]')
|
await page.click('[placeholder="Appleseed"]')
|
||||||
// Fill [placeholder="Appleseed"]
|
// Fill [placeholder="Appleseed"]
|
||||||
const last = 'last-' + generateId().slice(0, 4)
|
const last = 'last-' + generateId(4)
|
||||||
await page.fill('[placeholder="Appleseed"]', last)
|
await page.fill('[placeholder="Appleseed"]', last)
|
||||||
// Click button:has-text("Create")
|
// Click button:has-text("Create")
|
||||||
await page.click('button:has-text("Create")')
|
await page.click('button:has-text("Create")')
|
||||||
// Click text=q w
|
// Click text=q w
|
||||||
await page.click(`text=${first} ${last}`)
|
await page.click(`tr > :has-text("${first} ${last}")`)
|
||||||
// Click text=java
|
// Click text=java
|
||||||
await expect(page.locator('text=java').first()).toBeVisible()
|
await expect(page.locator('text=java').first()).toBeVisible()
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user