mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 11:31:57 +03:00
Custom done states (#1238)
This commit is contained in:
parent
5f4edc43c0
commit
944cfafcba
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Asset, getMetadata } from '@anticrm/platform'
|
import { Asset, getMetadata } from '@anticrm/platform'
|
||||||
import { AnySvelteComponent } from '../types';
|
import { AnySvelteComponent } from '../types'
|
||||||
|
|
||||||
export let icon: Asset | AnySvelteComponent
|
export let icon: Asset | AnySvelteComponent
|
||||||
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import { Applicant } from '@anticrm/recruit'
|
import { Applicant } from '@anticrm/recruit'
|
||||||
import task from '@anticrm/task'
|
import task from '@anticrm/task'
|
||||||
import { Button,Icon,IconAdd,Label,Scroller,SearchEdit,showPopup } from '@anticrm/ui'
|
import { Button,Icon,IconAdd,Label,Scroller,SearchEdit,showPopup } from '@anticrm/ui'
|
||||||
import { BuildModelKey } from '@anticrm/view';
|
import { BuildModelKey } from '@anticrm/view'
|
||||||
import { Table } from '@anticrm/view-resources'
|
import { Table } from '@anticrm/view-resources'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
import CreateApplication from './CreateApplication.svelte'
|
import CreateApplication from './CreateApplication.svelte'
|
||||||
|
@ -15,37 +15,33 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import type { Ref, Space, Doc, Class } from '@anticrm/core'
|
|
||||||
import { getClient, MessageBox } from '@anticrm/presentation'
|
import { getClient, MessageBox } from '@anticrm/presentation'
|
||||||
import { Label, Icon, showPopup, Component } from '@anticrm/ui'
|
import { Label, Icon, showPopup, Component } from '@anticrm/ui'
|
||||||
import type { KanbanTemplate, KanbanTemplateSpace, StateTemplate } from '@anticrm/task'
|
import type { DoneStateTemplate, KanbanTemplate, KanbanTemplateSpace, StateTemplate } from '@anticrm/task'
|
||||||
import setting from '../../plugin'
|
import setting from '../../plugin'
|
||||||
import task from '@anticrm/task'
|
import task from '@anticrm/task'
|
||||||
|
|
||||||
import Folders from './Folders.svelte'
|
import Folders from './Folders.svelte'
|
||||||
import Templates from './Templates.svelte'
|
import Templates from './Templates.svelte'
|
||||||
|
|
||||||
// export let objectId: Ref<Doc>
|
|
||||||
// export let space: Ref<Space>
|
|
||||||
// export let _class: Ref<Class<Doc>>
|
|
||||||
|
|
||||||
let folder: KanbanTemplateSpace | undefined
|
let folder: KanbanTemplateSpace | undefined
|
||||||
let template: KanbanTemplate | undefined
|
let template: KanbanTemplate | undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
function deleteState ({ state }: { state: StateTemplate }) {
|
function deleteState ({ state }: { state: StateTemplate | DoneStateTemplate }) {
|
||||||
if (template === undefined) {
|
if (template === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
showPopup(MessageBox, {
|
showPopup(MessageBox, {
|
||||||
label: setting.string.DeleteStatus,
|
label: setting.string.DeleteStatus,
|
||||||
message: setting.string.DeleteStatusConfirm
|
message: setting.string.DeleteStatusConfirm
|
||||||
}, undefined, async (result) => {
|
}, undefined, async (result) => {
|
||||||
if (result && template !== undefined) {
|
if (result && template !== undefined) {
|
||||||
await client.updateDoc(template._class, template.space, template._id, { $pull: { states: state._id } })
|
const collection = hierarchy.isDerived(state._class, task.class.DoneStateTemplate) ? 'doneStatesC' : 'statesC'
|
||||||
await client.removeCollection(state._class, template.space, state._id, template._id, template._class, 'statesC')
|
await client.removeCollection(state._class, template.space, state._id, template._id, template._class, collection)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Class, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
import { Class, DocumentQuery, FindOptions, Ref, SortingOrder } from '@anticrm/core'
|
||||||
import { createQuery } from '@anticrm/presentation'
|
import { createQuery } from '@anticrm/presentation'
|
||||||
import { DoneState, SpaceWithStates, State, Task } from '@anticrm/task'
|
import { DoneState, SpaceWithStates, State, Task } from '@anticrm/task'
|
||||||
import { ScrollBox } from '@anticrm/ui'
|
import { ScrollBox } from '@anticrm/ui'
|
||||||
@ -57,7 +57,13 @@
|
|||||||
{
|
{
|
||||||
space
|
space
|
||||||
},
|
},
|
||||||
(res) => (doneStates = res)
|
(res) => (doneStates = res),
|
||||||
|
{
|
||||||
|
sort: {
|
||||||
|
_class: SortingOrder.Descending,
|
||||||
|
rank: SortingOrder.Descending
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
async function updateQuery (search: string, selectedDoneStates: Set<Ref<DoneState>>): Promise<void> {
|
async function updateQuery (search: string, selectedDoneStates: Set<Ref<DoneState>>): Promise<void> {
|
||||||
@ -129,11 +135,20 @@
|
|||||||
<Label label={task.string.DoneStates} />
|
<Label label={task.string.DoneStates} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-row-center caption-color states">
|
<div class="flex-row-center caption-color states" class:antiStatesBar={doneStatusesView}>
|
||||||
{#if doneStatusesView}
|
{#if doneStatusesView}
|
||||||
|
<div
|
||||||
|
class="doneState withoutDone flex-center whitespace-nowrap"
|
||||||
|
class:disable={!withoutDone}
|
||||||
|
on:click={() => {
|
||||||
|
noDoneClick()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label label={task.string.NoDoneState}/>
|
||||||
|
</div>
|
||||||
{#each doneStates as state}
|
{#each doneStates as state}
|
||||||
<div
|
<div
|
||||||
class="doneState flex-row-center"
|
class="doneState flex-center whitespace-nowrap"
|
||||||
class:won={state._class === task.class.WonState}
|
class:won={state._class === task.class.WonState}
|
||||||
class:lost={state._class === task.class.LostState}
|
class:lost={state._class === task.class.LostState}
|
||||||
class:disable={!selectedDoneStates.has(state._id)}
|
class:disable={!selectedDoneStates.has(state._id)}
|
||||||
@ -151,15 +166,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div
|
|
||||||
class="doneState withoutDone flex-row-center"
|
|
||||||
class:disable={!withoutDone}
|
|
||||||
on:click={() => {
|
|
||||||
noDoneClick()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label label={task.string.NoDoneState}/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<StatesBar bind:state {space} on:change={() => updateQuery(search, selectedDoneStates)} />
|
<StatesBar bind:state {space} on:change={() => updateQuery(search, selectedDoneStates)} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -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 { AttachedDoc, Class, Doc, DocumentUpdate, FindOptions, Ref, SortingOrder } from '@anticrm/core'
|
||||||
|
import core from '@anticrm/core'
|
||||||
|
import { getResource } from '@anticrm/platform'
|
||||||
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
|
import type { Kanban, SpaceWithStates, State } from '@anticrm/task'
|
||||||
|
import task, { DoneState, LostState, WonState, DocWithRank, calcRank } from '@anticrm/task'
|
||||||
|
import { AnySvelteComponent, getPlatformColor, Grid } from '@anticrm/ui'
|
||||||
|
import { Loading, ScrollBox } from '@anticrm/ui'
|
||||||
|
import KanbanPanel from './KanbanPanel.svelte'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
|
export let kanban: Kanban
|
||||||
|
let wonStates: WonState[] = []
|
||||||
|
let lostStates: LostState[] = []
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const doneStatesQ = createQuery()
|
||||||
|
$: if (kanban !== undefined) {
|
||||||
|
doneStatesQ.query(
|
||||||
|
task.class.DoneState,
|
||||||
|
{ space: kanban.space },
|
||||||
|
(result) => {
|
||||||
|
wonStates = result.filter((x) => x._class === task.class.WonState)
|
||||||
|
lostStates = result.filter((x) => x._class === task.class.LostState)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let hoveredDoneState: Ref<DoneState> | undefined
|
||||||
|
|
||||||
|
const onDone = (state: DoneState) => async () => {
|
||||||
|
hoveredDoneState = undefined
|
||||||
|
dispatch('done', state)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="done-panel overflow-y-auto whitespace-nowrap">
|
||||||
|
{#each wonStates as wonState}
|
||||||
|
<div
|
||||||
|
class="flex-grow flex-center done-item"
|
||||||
|
class:hovered={hoveredDoneState === wonState._id}
|
||||||
|
on:dragenter={() => {
|
||||||
|
hoveredDoneState = wonState._id
|
||||||
|
}}
|
||||||
|
on:dragleave={() => {
|
||||||
|
if (hoveredDoneState === wonState._id) {
|
||||||
|
hoveredDoneState = undefined
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:dragover|preventDefault={() => {}}
|
||||||
|
on:drop={onDone(wonState)}>
|
||||||
|
<div class="done-icon won mr-2"/>
|
||||||
|
{wonState.title}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{#each lostStates as lostState}
|
||||||
|
<div
|
||||||
|
class="flex-grow flex-center done-item"
|
||||||
|
class:hovered={hoveredDoneState === lostState._id}
|
||||||
|
on:dragenter={() => {
|
||||||
|
hoveredDoneState = lostState._id
|
||||||
|
}}
|
||||||
|
on:dragleave={() => {
|
||||||
|
if (hoveredDoneState === lostState._id) {
|
||||||
|
hoveredDoneState = undefined
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:dragover|preventDefault={() => {}}
|
||||||
|
on:drop={onDone(lostState)}>
|
||||||
|
<div class="done-icon lost mr-2"/>
|
||||||
|
{lostState.title}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.done-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: stretch;
|
||||||
|
padding: .5rem 2.5rem;
|
||||||
|
background-color: var(--theme-bg-color);
|
||||||
|
border-top: 1px solid var(--theme-dialog-divider);
|
||||||
|
border-radius: 0 0 1.25rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done-item {
|
||||||
|
height: 3rem;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
border: 1px dashed transparent;
|
||||||
|
border-radius: .75rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
&.hovered {
|
||||||
|
background-color: var(--theme-button-bg-enabled);
|
||||||
|
border-color: var(--theme-dialog-divider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.done-icon {
|
||||||
|
width: .5rem;
|
||||||
|
height: .5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.won { background-color: #27B166; }
|
||||||
|
&.lost { background-color: #F96E50; }
|
||||||
|
}
|
||||||
|
</style>
|
@ -14,11 +14,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, SortingOrder } from '@anticrm/core'
|
import { Class,Ref,SortingOrder } from '@anticrm/core'
|
||||||
import { createQuery,getClient } from '@anticrm/presentation'
|
import { createQuery,getClient } from '@anticrm/presentation'
|
||||||
import type { Kanban, State, DoneState } from '@anticrm/task'
|
import type { DoneState,Kanban,State } from '@anticrm/task'
|
||||||
import task,{ calcRank } from '@anticrm/task'
|
import task,{ calcRank } from '@anticrm/task'
|
||||||
|
|
||||||
import StatesEditor from '../state/StatesEditor.svelte'
|
import StatesEditor from '../state/StatesEditor.svelte'
|
||||||
|
|
||||||
export let kanban: Kanban
|
export let kanban: Kanban
|
||||||
@ -32,6 +31,7 @@
|
|||||||
$: lostStates = doneStates.filter((x) => x._class === task.class.LostState)
|
$: lostStates = doneStates.filter((x) => x._class === task.class.LostState)
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
const statesQ = createQuery()
|
const statesQ = createQuery()
|
||||||
$: statesQ.query(task.class.State, { space: kanban.space }, result => { states = result}, {
|
$: statesQ.query(task.class.State, { space: kanban.space }, result => { states = result}, {
|
||||||
@ -65,19 +65,25 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onAdd () {
|
async function onAdd (_class: Ref<Class<State | DoneState>>) {
|
||||||
const lastOne = await client.findOne(
|
const lastOne = await client.findOne(
|
||||||
task.class.State,
|
_class,
|
||||||
{ space: kanban.space },
|
{ space: kanban.space },
|
||||||
{ sort: { rank: SortingOrder.Descending } }
|
{ sort: { rank: SortingOrder.Descending } }
|
||||||
)
|
)
|
||||||
|
if (hierarchy.isDerived(_class, task.class.DoneState)) {
|
||||||
|
await client.createDoc(_class, kanban.space, {
|
||||||
|
title: 'New Done State',
|
||||||
|
rank: calcRank(lastOne, undefined)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
await client.createDoc(task.class.State, kanban.space, {
|
await client.createDoc(task.class.State, kanban.space, {
|
||||||
title: 'New State',
|
title: 'New State',
|
||||||
color: 9,
|
color: 9,
|
||||||
rank: calcRank(lastOne, undefined)
|
rank: calcRank(lastOne, undefined)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<StatesEditor {states} {wonStates} {lostStates} on:add={onAdd} on:delete on:move={onMove}/>
|
<StatesEditor {states} {wonStates} {lostStates} on:add={(e) => { onAdd(e.detail) }} on:delete on:move={onMove}/>
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { Ref, Space, SortingOrder } from '@anticrm/core'
|
import { Ref, Space, SortingOrder, Class } from '@anticrm/core'
|
||||||
import core from '@anticrm/core'
|
import core from '@anticrm/core'
|
||||||
import { createQuery, getClient } from '@anticrm/presentation'
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
import type { State, DoneStateTemplate, KanbanTemplate, StateTemplate } from '@anticrm/task'
|
import type { State, DoneStateTemplate, KanbanTemplate, StateTemplate, DoneState } from '@anticrm/task'
|
||||||
import task, { calcRank } from '@anticrm/task'
|
import task, { calcRank } from '@anticrm/task'
|
||||||
|
|
||||||
import StatesEditor from '../state/StatesEditor.svelte'
|
import StatesEditor from '../state/StatesEditor.svelte'
|
||||||
@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
const statesQ = createQuery()
|
const statesQ = createQuery()
|
||||||
$: statesQ.query(task.class.StateTemplate, { attachedTo: kanban._id }, result => { states = result }, {
|
$: statesQ.query(task.class.StateTemplate, { attachedTo: kanban._id }, result => { states = result }, {
|
||||||
@ -71,13 +72,27 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onAdd () {
|
async function onAdd (_class: Ref<Class<State | DoneState>>) {
|
||||||
const lastOne = await client.findOne(
|
const lastOne = await client.findOne(
|
||||||
task.class.StateTemplate,
|
task.class.StateTemplate,
|
||||||
{ attachedTo: kanban._id },
|
{ attachedTo: kanban._id },
|
||||||
{ sort: { rank: SortingOrder.Descending } }
|
{ sort: { rank: SortingOrder.Descending } }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (hierarchy.isDerived(_class, task.class.DoneState)) {
|
||||||
|
const targetClass = _class === task.class.WonState ? task.class.WonStateTemplate : task.class.LostStateTemplate
|
||||||
|
await client.addCollection(
|
||||||
|
targetClass,
|
||||||
|
kanban.space,
|
||||||
|
kanban._id,
|
||||||
|
kanban._class,
|
||||||
|
'doneStatesC',
|
||||||
|
{
|
||||||
|
title: 'New Done State',
|
||||||
|
rank: calcRank(lastOne, undefined)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
await client.addCollection(
|
await client.addCollection(
|
||||||
task.class.StateTemplate,
|
task.class.StateTemplate,
|
||||||
kanban.space,
|
kanban.space,
|
||||||
@ -92,7 +107,9 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDelete ({ detail: { state } }: { detail: { state: State }}) {
|
}
|
||||||
|
|
||||||
|
function onDelete ({ detail: { state } }: { detail: { state: State | DoneState }}) {
|
||||||
if (space === undefined) {
|
if (space === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -101,4 +118,4 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<StatesEditor {states} {wonStates} {lostStates} on:add={onAdd} on:delete={onDelete} on:move={onMove}/>
|
<StatesEditor {states} {wonStates} {lostStates} on:add={(e) => { onAdd(e.detail) }} on:delete={onDelete} on:move={onMove}/>
|
||||||
|
@ -15,14 +15,13 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachedDoc, Class, Doc, DocumentUpdate, FindOptions, Ref, SortingOrder } from '@anticrm/core'
|
import core,{ AttachedDoc,Class,Doc,DocumentUpdate,FindOptions,Ref,SortingOrder } from '@anticrm/core'
|
||||||
import core from '@anticrm/core'
|
|
||||||
import { getResource } from '@anticrm/platform'
|
import { getResource } from '@anticrm/platform'
|
||||||
import { createQuery,getClient } from '@anticrm/presentation'
|
import { createQuery,getClient } from '@anticrm/presentation'
|
||||||
import type { Kanban,SpaceWithStates,State } from '@anticrm/task'
|
import type { Kanban,SpaceWithStates,State } from '@anticrm/task'
|
||||||
import task, { DoneState, LostState, WonState, DocWithRank, calcRank } from '@anticrm/task'
|
import task,{ calcRank,DocWithRank,DoneState } from '@anticrm/task'
|
||||||
import { AnySvelteComponent, getPlatformColor } from '@anticrm/ui'
|
import { AnySvelteComponent,getPlatformColor,Loading,ScrollBox } from '@anticrm/ui'
|
||||||
import { Loading, ScrollBox } from '@anticrm/ui'
|
import KanbanDragDone from './KanbanDragDone.svelte'
|
||||||
import KanbanPanel from './KanbanPanel.svelte'
|
import KanbanPanel from './KanbanPanel.svelte'
|
||||||
// import KanbanPanelEmpty from './KanbanPanelEmpty.svelte'
|
// import KanbanPanelEmpty from './KanbanPanelEmpty.svelte'
|
||||||
|
|
||||||
@ -39,8 +38,6 @@
|
|||||||
let states: State[] = []
|
let states: State[] = []
|
||||||
|
|
||||||
let objects: Item[] = []
|
let objects: Item[] = []
|
||||||
let wonState: WonState | undefined
|
|
||||||
let lostState: LostState | undefined
|
|
||||||
|
|
||||||
const kanbanQuery = createQuery()
|
const kanbanQuery = createQuery()
|
||||||
$: kanbanQuery.query(task.class.Kanban, { attachedTo: space }, result => { kanban = result[0] })
|
$: kanbanQuery.query(task.class.Kanban, { attachedTo: space }, result => { kanban = result[0] })
|
||||||
@ -54,17 +51,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const doneStatesQ = createQuery()
|
|
||||||
$: if (kanban !== undefined) {
|
|
||||||
doneStatesQ.query(
|
|
||||||
task.class.DoneState,
|
|
||||||
{ space: kanban.space, ...search !== '' ? {$search: search} : {} },
|
|
||||||
(result) => {
|
|
||||||
wonState = result.find((x) => x._class === task.class.WonState)
|
|
||||||
lostState = result.find((x) => x._class === task.class.LostState)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const objsQ = createQuery()
|
const objsQ = createQuery()
|
||||||
$: objsQ.query(
|
$: objsQ.query(
|
||||||
_class,
|
_class,
|
||||||
@ -164,14 +150,12 @@
|
|||||||
return await getResource(presenterMixin.card)
|
return await getResource(presenterMixin.card)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDone = (state: DoneState) => async () => {
|
async function onDone (state: DoneState): Promise<void> {
|
||||||
isDragging = false
|
isDragging = false
|
||||||
hoveredDoneState = undefined
|
|
||||||
await updateItem(dragCard, { doneState: state._id })
|
await updateItem(dragCard, { doneState: state._id })
|
||||||
}
|
}
|
||||||
|
|
||||||
let isDragging = false
|
let isDragging = false
|
||||||
let hoveredDoneState: Ref<DoneState> | undefined
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await cardPresenter(_class)}
|
{#await cardPresenter(_class)}
|
||||||
@ -227,37 +211,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</ScrollBox>
|
</ScrollBox>
|
||||||
</div>
|
</div>
|
||||||
{#if isDragging && wonState !== undefined && lostState !== undefined}
|
{#if isDragging}
|
||||||
<div class="done-panel">
|
<KanbanDragDone {kanban} on:done={(e) => { onDone(e.detail) }} />
|
||||||
<div
|
|
||||||
class="flex-grow flex-center done-item"
|
|
||||||
class:hovered={hoveredDoneState === wonState._id}
|
|
||||||
on:dragenter={() => {
|
|
||||||
hoveredDoneState = wonState?._id
|
|
||||||
}}
|
|
||||||
on:dragleave={() => {
|
|
||||||
hoveredDoneState = undefined
|
|
||||||
}}
|
|
||||||
on:dragover|preventDefault={() => {}}
|
|
||||||
on:drop={onDone(wonState)}>
|
|
||||||
<div class="done-icon won mr-2"/>
|
|
||||||
{wonState.title}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex-grow flex-center done-item"
|
|
||||||
class:hovered={hoveredDoneState === lostState._id}
|
|
||||||
on:dragenter={() => {
|
|
||||||
hoveredDoneState = lostState?._id
|
|
||||||
}}
|
|
||||||
on:dragleave={() => {
|
|
||||||
hoveredDoneState = undefined
|
|
||||||
}}
|
|
||||||
on:dragover|preventDefault={() => {}}
|
|
||||||
on:drop={onDone(lostState)}>
|
|
||||||
<div class="done-icon lost mr-2"/>
|
|
||||||
{lostState.title}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
@ -273,42 +228,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.done-panel {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: stretch;
|
|
||||||
padding: .5rem 2.5rem;
|
|
||||||
background-color: var(--theme-bg-color);
|
|
||||||
border-top: 1px solid var(--theme-dialog-divider);
|
|
||||||
border-radius: 0 0 1.25rem 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.done-item {
|
|
||||||
height: 3rem;
|
|
||||||
color: var(--theme-caption-color);
|
|
||||||
border: 1px dashed transparent;
|
|
||||||
border-radius: .75rem;
|
|
||||||
|
|
||||||
&.hovered {
|
|
||||||
background-color: var(--theme-button-bg-enabled);
|
|
||||||
border-color: var(--theme-dialog-divider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.done-icon {
|
|
||||||
width: .5rem;
|
|
||||||
height: .5rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
&.won { background-color: #27B166; }
|
|
||||||
&.lost { background-color: #F96E50; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable {
|
.scrollable {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-bottom: .25rem;
|
margin-bottom: .25rem;
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sort: {
|
sort: {
|
||||||
|
_class: SortingOrder.Descending,
|
||||||
rank: SortingOrder.Ascending
|
rank: SortingOrder.Ascending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Class, Obj, Ref } from '@anticrm/core'
|
import type { Class, Doc, DocumentQuery, Obj, Ref } from '@anticrm/core'
|
||||||
import core from '@anticrm/core'
|
import core from '@anticrm/core'
|
||||||
import { createQuery, getClient, MessageBox } from '@anticrm/presentation'
|
import { createQuery, getClient, MessageBox } from '@anticrm/presentation'
|
||||||
import type { Kanban, SpaceWithStates, State } from '@anticrm/task'
|
import type { DoneState, Kanban, SpaceWithStates, State } from '@anticrm/task'
|
||||||
import task from '../../plugin'
|
import task from '../../plugin'
|
||||||
import KanbanEditor from '../kanban/KanbanEditor.svelte'
|
import KanbanEditor from '../kanban/KanbanEditor.svelte'
|
||||||
import { Icon, IconClose, Label, showPopup, ActionIcon, ScrollBox } from '@anticrm/ui'
|
import { Icon, IconClose, Label, showPopup, ActionIcon, ScrollBox } from '@anticrm/ui'
|
||||||
@ -33,6 +33,7 @@
|
|||||||
let spaceInstance: SpaceWithStates | undefined
|
let spaceInstance: SpaceWithStates | undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const kanbanQ = createQuery()
|
const kanbanQ = createQuery()
|
||||||
@ -44,7 +45,7 @@
|
|||||||
const spaceI = createQuery()
|
const spaceI = createQuery()
|
||||||
$: spaceI.query<SpaceWithStates>(spaceClass, { _id: _id }, result => { spaceInstance = result.shift() })
|
$: spaceI.query<SpaceWithStates>(spaceClass, { _id: _id }, result => { spaceInstance = result.shift() })
|
||||||
|
|
||||||
async function deleteState ({ state }: { state: State }) {
|
async function deleteState ({ state }: { state: State | DoneState }) {
|
||||||
if (spaceInstance === undefined) {
|
if (spaceInstance === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -53,7 +54,14 @@
|
|||||||
const spaceView = client.getHierarchy().as(spaceClassInstance, workbench.mixin.SpaceView)
|
const spaceView = client.getHierarchy().as(spaceClassInstance, workbench.mixin.SpaceView)
|
||||||
const containingClass = spaceView.view.class
|
const containingClass = spaceView.view.class
|
||||||
|
|
||||||
const objectsInThisState = await client.findAll(containingClass, { state: state._id })
|
let query: DocumentQuery<Doc>
|
||||||
|
if (hierarchy.isDerived(state._class, task.class.DoneState)) {
|
||||||
|
query = { doneState: state._id }
|
||||||
|
} else {
|
||||||
|
query = { state: state._id }
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectsInThisState = await client.findAll(containingClass, query)
|
||||||
|
|
||||||
if (objectsInThisState.length > 0) {
|
if (objectsInThisState.length > 0) {
|
||||||
showPopup(MessageBox, {
|
showPopup(MessageBox, {
|
||||||
@ -66,7 +74,6 @@
|
|||||||
message: task.string.StatusDeleteConfirm
|
message: task.string.StatusDeleteConfirm
|
||||||
}, undefined, async (result) => {
|
}, undefined, async (result) => {
|
||||||
if (result && kanban !== undefined) {
|
if (result && kanban !== undefined) {
|
||||||
await client.updateDoc(kanban._class, kanban.space, kanban._id, { $pull: { states: state._id } })
|
|
||||||
client.removeDoc(state._class, state.space, state._id)
|
client.removeDoc(state._class, state.space, state._id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref } from '@anticrm/core'
|
import { Class, Ref } from '@anticrm/core'
|
||||||
import { AttributeEditor, getClient } from '@anticrm/presentation'
|
import { AttributeEditor, getClient } from '@anticrm/presentation'
|
||||||
import type { DoneState, State } from '@anticrm/task'
|
import type { DoneState, State } from '@anticrm/task'
|
||||||
import { CircleButton, IconAdd, IconMoreH, Label, showPopup, getPlatformColor } from '@anticrm/ui'
|
import { CircleButton, IconAdd, IconMoreH, Label, showPopup, getPlatformColor } from '@anticrm/ui'
|
||||||
@ -69,17 +69,17 @@
|
|||||||
await client.updateDoc(state._class, state.space, state._id, { color })
|
await client.updateDoc(state._class, state.space, state._id, { color })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onAdd () {
|
async function onAdd (_class: Ref<Class<State | DoneState>>) {
|
||||||
dispatch('add')
|
dispatch('add', _class)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-col">
|
<div>
|
||||||
<div class="flex-no-shrink flex-between trans-title uppercase">
|
<div class="flex-no-shrink flex-between trans-title uppercase">
|
||||||
<Label label={task.string.ActiveStates} />
|
<Label label={task.string.ActiveStates} />
|
||||||
<CircleButton icon={IconAdd} size={'medium'} on:click={onAdd}/>
|
<CircleButton icon={IconAdd} size={'medium'} on:click={() => { onAdd(task.class.State) }}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-y-auto mt-3">
|
<div class="mt-3">
|
||||||
{#each states as state, i}
|
{#each states as state, i}
|
||||||
{#if state}
|
{#if state}
|
||||||
<div bind:this={elements[i]} class="flex-between states" draggable={true}
|
<div bind:this={elements[i]} class="flex-between states" draggable={true}
|
||||||
@ -116,33 +116,53 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col mt-9">
|
<div class="mt-9">
|
||||||
<div class="flex-no-shrink trans-title uppercase">
|
<div class="flex-no-shrink flex-between trans-title uppercase">
|
||||||
<Label label={task.string.DoneStatesWon} />
|
<Label label={task.string.DoneStatesWon} />
|
||||||
|
<CircleButton icon={IconAdd} size={'medium'} on:click={() => { onAdd(task.class.WonState) }}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-y-auto mt-4">
|
<div class="mt-4">
|
||||||
{#each wonStates as state}
|
{#each wonStates as state}
|
||||||
{#if state}
|
{#if state}
|
||||||
<div class="states flex-row-center">
|
<div class="states flex-row-center">
|
||||||
<div class="bar"/>
|
<div class="bar"/>
|
||||||
<div class="color" style="background-color: #a5d179"/>
|
<div class="color" style="background-color: #a5d179"/>
|
||||||
<div class="flex-grow caption-color"><AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="title"/></div>
|
<div class="flex-grow caption-color"><AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="title"/></div>
|
||||||
|
{#if wonStates.length > 1}
|
||||||
|
<div class="tool hover-trans"
|
||||||
|
on:click={(ev) => {
|
||||||
|
showPopup(StatusesPopup, { onDelete: () => dispatch('delete', { state }) }, ev.target, () => {})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconMoreH size={'medium'} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col mt-9">
|
<div class="mt-9">
|
||||||
<div class="flex-no-shrink trans-title uppercase">
|
<div class="flex-no-shrink flex-between trans-title uppercase">
|
||||||
<Label label={task.string.DoneStatesLost} />
|
<Label label={task.string.DoneStatesLost} />
|
||||||
|
<CircleButton icon={IconAdd} size={'medium'} on:click={() => { onAdd(task.class.LostState) }}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-y-auto mt-4">
|
<div class="mt-4">
|
||||||
{#each lostStates as state}
|
{#each lostStates as state}
|
||||||
{#if state}
|
{#if state}
|
||||||
<div class="states flex-row-center">
|
<div class="states flex-row-center">
|
||||||
<div class="bar"/>
|
<div class="bar"/>
|
||||||
<div class="color" style="background-color: #f28469"/>
|
<div class="color" style="background-color: #f28469"/>
|
||||||
<div class="flex-grow caption-color"><AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="title"/></div>
|
<div class="flex-grow caption-color"><AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="title"/></div>
|
||||||
|
{#if lostStates.length > 1}
|
||||||
|
<div class="tool hover-trans"
|
||||||
|
on:click={(ev) => {
|
||||||
|
showPopup(StatusesPopup, { onDelete: () => dispatch('delete', { state }) }, ev.target, () => {})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconMoreH size={'medium'} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
Loading…
Reference in New Issue
Block a user