mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 19:44:59 +03:00
Merge pull request #501 from hcengineering/task-states
Add Task Kanban and States
This commit is contained in:
commit
15ec138a80
@ -15,25 +15,26 @@
|
||||
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import contact from '@anticrm/contact'
|
||||
import type { Doc, Domain, FindOptions, Ref } from '@anticrm/core'
|
||||
import type { Doc, DocWithState, Domain, FindOptions, Ref } from '@anticrm/core'
|
||||
import { Builder, Model, Prop, TypeString, UX } from '@anticrm/model'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import core, { TDoc, TSpace } from '@anticrm/model-core'
|
||||
import core, { TDoc, TSpaceWithStates } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import type { Project, Task } from '@anticrm/task'
|
||||
import { createProjectKanban } from '@anticrm/task-resources'
|
||||
import task from './plugin'
|
||||
|
||||
@Model(task.class.Project, core.class.Space)
|
||||
@Model(task.class.Project, core.class.SpaceWithStates)
|
||||
@UX('Project' as IntlString, task.icon.Task)
|
||||
export class TProject extends TSpace implements Project {}
|
||||
export class TProject extends TSpaceWithStates implements Project {}
|
||||
|
||||
@Model(task.class.Task, core.class.Doc, 'task' as Domain)
|
||||
@Model(task.class.Task, core.class.Doc, 'task' as Domain, [core.interface.DocWithState])
|
||||
@UX('Task' as IntlString, task.icon.Task, 'TASK' as IntlString)
|
||||
export class TTask extends TDoc implements Task {
|
||||
@Prop(TypeString(), 'No.' as IntlString)
|
||||
number!: number
|
||||
declare number: DocWithState['number']
|
||||
declare state: DocWithState['state']
|
||||
|
||||
@Prop(TypeString(), 'Name' as IntlString)
|
||||
name!: string
|
||||
@ -47,6 +48,9 @@ export class TTask extends TDoc implements Task {
|
||||
@Prop(TypeString(), 'Comments' as IntlString)
|
||||
comments!: number
|
||||
|
||||
@Prop(TypeString(), 'Attachments' as IntlString)
|
||||
attachments!: number
|
||||
|
||||
@Prop(TypeString(), 'Labels' as IntlString)
|
||||
labels!: string
|
||||
}
|
||||
@ -104,6 +108,29 @@ export function createModel (builder: Builder): void {
|
||||
editor: task.component.EditTask
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Sequence, view.space.Sequence, {
|
||||
attachedTo: task.class.Task,
|
||||
sequence: 0
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: task.class.Task,
|
||||
descriptor: view.viewlet.Kanban,
|
||||
open: task.component.EditTask,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
options: {
|
||||
lookup: {
|
||||
assignee: contact.class.EmployeeAccount,
|
||||
state: core.class.State
|
||||
}
|
||||
} as FindOptions<Doc>, // TODO: fix
|
||||
config: ['$lookup.attachedTo', '$lookup.state']
|
||||
})
|
||||
|
||||
builder.mixin(task.class.Task, core.class.Class, view.mixin.KanbanCard, {
|
||||
card: task.component.KanbanCard
|
||||
})
|
||||
|
||||
builder.createDoc(task.class.Project, core.space.Model, {
|
||||
name: 'public',
|
||||
description: 'Public tasks',
|
||||
@ -111,8 +138,8 @@ export function createModel (builder: Builder): void {
|
||||
members: []
|
||||
}, task.space.TasksPublic)
|
||||
|
||||
builder.createDoc(view.class.Sequence, view.space.Sequence, {
|
||||
attachedTo: task.class.Task,
|
||||
sequence: 0
|
||||
})
|
||||
createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => {
|
||||
builder.createDoc(_class, space, data, id)
|
||||
return await Promise.resolve()
|
||||
}).catch((err) => console.error(err))
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ export default mergeIds(taskId, task, {
|
||||
CreateProject: '' as AnyComponent,
|
||||
CreateTask: '' as AnyComponent,
|
||||
EditTask: '' as AnyComponent,
|
||||
TaskPresenter: '' as AnyComponent
|
||||
TaskPresenter: '' as AnyComponent,
|
||||
KanbanCard: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Task: '' as IntlString,
|
||||
|
@ -15,6 +15,7 @@
|
||||
"TaskDescription": "Description",
|
||||
"UploadDropFilesHere": "Upload or drop files here",
|
||||
"NoAttachmentsForTask": "There are no attachments for this task.",
|
||||
"AssigneeRequired": "Assignee is required"
|
||||
"AssigneeRequired": "Assignee is required",
|
||||
"More": "Options"
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@
|
||||
"@anticrm/panel": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/view-resources": "~0.6.0",
|
||||
"@anticrm/login": "~0.6.1"
|
||||
"@anticrm/login": "~0.6.1",
|
||||
"@anticrm/chunter-resources": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Attachment } from '@anticrm/chunter'
|
||||
import chunter from '@anticrm/chunter'
|
||||
@ -32,7 +31,9 @@
|
||||
let attachments: Attachment[] = []
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(chunter.class.Attachment, { attachedTo: objectId }, result => { attachments = result })
|
||||
$: query.query(chunter.class.Attachment, { attachedTo: objectId }, (result) => {
|
||||
attachments = result
|
||||
})
|
||||
|
||||
let inputFile: HTMLInputElement
|
||||
let loading = 0
|
||||
@ -84,17 +85,37 @@
|
||||
<div class="flex-row-center">
|
||||
<span class="title">Attachments</span>
|
||||
{#if loading}
|
||||
<Spinner/>
|
||||
<Spinner />
|
||||
{:else}
|
||||
<CircleButton icon={IconAdd} size={'small'} on:click={ () => { inputFile.click() } } />
|
||||
<CircleButton
|
||||
icon={IconAdd}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
inputFile.click()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<input bind:this={inputFile} multiple type="file" name="file" id="file" style="display: none" on:change={fileSelected}/>
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
multiple
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
style="display: none"
|
||||
on:change={fileSelected}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if attachments.length === 0 && !loading}
|
||||
<div class="flex-col-center mt-5 zone-container" class:solid={dragover}
|
||||
on:dragover|preventDefault={ () => { dragover = true } }
|
||||
on:dragleave={ () => { dragover = false } }
|
||||
<div
|
||||
class="flex-col-center mt-5 zone-container"
|
||||
class:solid={dragover}
|
||||
on:dragover|preventDefault={() => {
|
||||
dragover = true
|
||||
}}
|
||||
on:dragleave={() => {
|
||||
dragover = false
|
||||
}}
|
||||
on:drop|preventDefault|stopPropagation={fileDrop}
|
||||
>
|
||||
<UploadDuo size={'large'} />
|
||||
@ -106,11 +127,11 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<Table
|
||||
<Table
|
||||
_class={chunter.class.Attachment}
|
||||
config={['', 'lastModified']}
|
||||
options={ {} }
|
||||
query={ { attachedTo: objectId } }
|
||||
options={{}}
|
||||
query={{ attachedTo: objectId }}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@ -121,7 +142,7 @@
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
margin-right: .75rem;
|
||||
margin-right: 0.75rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
@ -131,8 +152,8 @@
|
||||
.zone-container {
|
||||
padding: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
background: rgba(255, 255, 255, .03);
|
||||
border: 1px dashed rgba(255, 255, 255, .16);
|
||||
border-radius: .75rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.16);
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -12,15 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { IconFolder, EditBox, ToggleWithLabel, Grid } from '@anticrm/ui'
|
||||
|
||||
import core, { generateId, Ref } from '@anticrm/core'
|
||||
import { getClient, SpaceCreateCard } from '@anticrm/presentation'
|
||||
|
||||
import { Project } from '@anticrm/task'
|
||||
import { EditBox, Grid, IconFolder, ToggleWithLabel } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import task from '../plugin'
|
||||
import core from '@anticrm/core'
|
||||
import { createProjectKanban } from '../utils'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -33,24 +32,36 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
function createProject () {
|
||||
client.createDoc(task.class.Project, core.space.Model, {
|
||||
name,
|
||||
description,
|
||||
private: false,
|
||||
members: []
|
||||
async function createProject (): Promise<void> {
|
||||
const id: Ref<Project> = generateId()
|
||||
await client.createDoc(
|
||||
task.class.Project,
|
||||
core.space.Model,
|
||||
{
|
||||
name,
|
||||
description,
|
||||
private: false,
|
||||
members: []
|
||||
},
|
||||
id
|
||||
)
|
||||
|
||||
await createProjectKanban(id, async (_class, space, data, id) => {
|
||||
await client.createDoc(_class, space, data, id)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<SpaceCreateCard
|
||||
label={task.string.CreateProject}
|
||||
label={task.string.CreateProject}
|
||||
okAction={createProject}
|
||||
canSave={name.length > 0}
|
||||
on:close={() => { dispatch('close') }}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox label={task.string.ProjectName} icon={IconFolder} bind:value={name} placeholder={'Project name'} focus/>
|
||||
<ToggleWithLabel label={task.string.MakePrivate} description={task.string.MakePrivateDescription}/>
|
||||
<EditBox label={task.string.ProjectName} icon={IconFolder} bind:value={name} placeholder={'Project name'} focus />
|
||||
<ToggleWithLabel label={task.string.MakePrivate} description={task.string.MakePrivateDescription} />
|
||||
</Grid>
|
||||
</SpaceCreateCard>
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import type { Data, Ref, Space } from '@anticrm/core'
|
||||
@ -31,14 +30,14 @@
|
||||
const status: Status = OK
|
||||
|
||||
let assignee: Ref<EmployeeAccount> // | null = null
|
||||
|
||||
|
||||
const object: Data<Task> = {
|
||||
name: '',
|
||||
description: '',
|
||||
assignee: undefined as unknown as Ref<Employee>,
|
||||
number: 0
|
||||
}
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const taskId = generateId()
|
||||
@ -52,11 +51,17 @@
|
||||
if (sequence === undefined) {
|
||||
throw new Error('sequence object not found')
|
||||
}
|
||||
|
||||
const incResult = await client.updateDoc(view.class.Sequence, view.space.Sequence, sequence._id, {
|
||||
$inc: { sequence: 1 }
|
||||
}, true)
|
||||
|
||||
|
||||
const incResult = await client.updateDoc(
|
||||
view.class.Sequence,
|
||||
view.space.Sequence,
|
||||
sequence._id,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
const value: Data<Task> = {
|
||||
name: object.name,
|
||||
description: object.description,
|
||||
@ -71,32 +76,50 @@
|
||||
|
||||
<!-- <DialogHeader {space} {object} {newValue} {resume} create={true} on:save={createCandidate}/> -->
|
||||
|
||||
<Card label={task.string.CreateTask}
|
||||
okAction={createTask}
|
||||
canSave={object.name.length > 0 && assignee !== undefined}
|
||||
spaceClass={task.class.Project}
|
||||
spaceLabel={task.string.ProjectName}
|
||||
spacePlaceholder={task.string.SelectProject}
|
||||
bind:space={_space}
|
||||
on:close={() => { dispatch('close') }}>
|
||||
<StatusControl slot="error" {status} />
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox label={task.string.TaskName} bind:value={object.name} icon={task.icon.Task} placeholder="The boring task" maxWidth="39rem" focus/>
|
||||
<UserBox _class={contact.class.EmployeeAccount} title='Assignee *' caption='Assign this task' bind:value={assignee} />
|
||||
</Grid>
|
||||
<Card
|
||||
label={task.string.CreateTask}
|
||||
okAction={createTask}
|
||||
canSave={object.name.length > 0 && assignee !== undefined}
|
||||
spaceClass={task.class.Project}
|
||||
spaceLabel={task.string.ProjectName}
|
||||
spacePlaceholder={task.string.SelectProject}
|
||||
bind:space={_space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<StatusControl slot="error" {status} />
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox
|
||||
label={task.string.TaskName}
|
||||
bind:value={object.name}
|
||||
icon={task.icon.Task}
|
||||
placeholder="The boring task"
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
/>
|
||||
<UserBox
|
||||
_class={contact.class.EmployeeAccount}
|
||||
title="Assignee *"
|
||||
caption="Assign this task"
|
||||
bind:value={assignee}
|
||||
/>
|
||||
</Grid>
|
||||
</Card>
|
||||
|
||||
<style lang="scss">
|
||||
.channels {
|
||||
margin-top: 1.25rem;
|
||||
span { margin-left: .5rem; }
|
||||
span {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.locations {
|
||||
span {
|
||||
margin-bottom: .125rem;
|
||||
margin-bottom: 0.125rem;
|
||||
font-weight: 500;
|
||||
font-size: .75rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--theme-content-accent-color);
|
||||
}
|
||||
|
||||
@ -104,7 +127,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: .75rem;
|
||||
margin-top: 0.75rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
}
|
||||
@ -117,12 +140,14 @@
|
||||
|
||||
.resume {
|
||||
margin-top: 1rem;
|
||||
padding: .75rem;
|
||||
background: rgba(255, 255, 255, .05);
|
||||
border: 1px dashed rgba(255, 255, 255, .2);
|
||||
border-radius: .5rem;
|
||||
padding: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
backdrop-filter: blur(10px);
|
||||
&.solid { border-style: solid; }
|
||||
&.solid {
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
// .resume a {
|
||||
// font-size: .75rem;
|
||||
|
@ -12,24 +12,25 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Ref } from '@anticrm/core'
|
||||
import { Panel } from '@anticrm/panel'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import type { Task } from '@anticrm/task'
|
||||
import { EditBox, Grid } from '@anticrm/ui'
|
||||
import { EditBox, Grid } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import task from '../plugin'
|
||||
import Attachments from './Attachments.svelte'
|
||||
import TaskHeader from './TaskHeader.svelte'
|
||||
|
||||
|
||||
export let _id: Ref<Task>
|
||||
let object: Task
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(task.class.Task, { _id }, result => { object = result[0] })
|
||||
$: query.query(task.class.Task, { _id }, (result) => {
|
||||
object = result[0]
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@ -40,19 +41,40 @@ import { EditBox, Grid } from '@anticrm/ui'
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<Panel icon={view.icon.Table} title={object.name} {object} on:close={() => { dispatch('close') }}>
|
||||
<TaskHeader {object} slot="subtitle" />
|
||||
<Panel
|
||||
icon={view.icon.Table}
|
||||
title={object.name}
|
||||
{object}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<TaskHeader {object} slot="subtitle" />
|
||||
|
||||
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox label={task.string.TaskName} bind:value={object.name} icon={task.icon.Task} placeholder="The boring task" maxWidth="39rem" focus on:change={(evt) => change('name', object.name)}/>
|
||||
<EditBox label={task.string.TaskDescription} bind:value={object.description} icon={task.icon.Task} placeholder="Description" maxWidth="39rem" on:change={(evt) => change('description', object.description)}/>
|
||||
</Grid>
|
||||
|
||||
<div class="mt-14">
|
||||
<Attachments objectId={object._id} _class={object._class} space={object.space} />
|
||||
</div>
|
||||
</Panel>
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox
|
||||
label={task.string.TaskName}
|
||||
bind:value={object.name}
|
||||
icon={task.icon.Task}
|
||||
placeholder="The boring task"
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
on:change={(evt) => change('name', object.name)}
|
||||
/>
|
||||
<EditBox
|
||||
label={task.string.TaskDescription}
|
||||
bind:value={object.description}
|
||||
icon={task.icon.Task}
|
||||
placeholder="Description"
|
||||
maxWidth="39rem"
|
||||
on:change={(evt) => change('description', object.description)}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<div class="mt-14">
|
||||
<Attachments objectId={object._id} _class={object._class} space={object.space} />
|
||||
</div>
|
||||
</Panel>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
91
plugins/task-resources/src/components/KanbanCard.svelte
Normal file
91
plugins/task-resources/src/components/KanbanCard.svelte
Normal file
@ -0,0 +1,91 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { AttachmentsPresenter, CommentsPresenter } from '@anticrm/chunter-resources'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
import type { WithLookup } from '@anticrm/core'
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import type { Task } from '@anticrm/task'
|
||||
import { ActionIcon, IconMoreH, Label, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
import task from '../plugin'
|
||||
import TaskPresenter from './TaskPresenter.svelte'
|
||||
|
||||
export let object: WithLookup<Task>
|
||||
export let draggable: boolean
|
||||
|
||||
const showMenu = (ev?: Event): void => {
|
||||
showPopup(ContextMenu, { object }, (ev as MouseEvent).target as HTMLElement)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
||||
<div class="content">
|
||||
<div class="flex-row-center">
|
||||
<div class="flex-col ml-2">
|
||||
<div class="sm-tool-icon step-lr75">
|
||||
<TaskPresenter value={object} />
|
||||
</div>
|
||||
<div class="fs-title">{object.name}</div>
|
||||
<div class="small-text">{object.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-between">
|
||||
{#if object.$lookup?.assignee}
|
||||
<div class="flex-center safari-gap-1">
|
||||
<Avatar avatar={object.$lookup?.assignee?.avatar} size={'x-small'} />
|
||||
<Label label={formatName(object.$lookup?.assignee?.name)} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-row-reverse">
|
||||
<ActionIcon
|
||||
label={task.string.More}
|
||||
action={(evt) => {
|
||||
showMenu(evt)
|
||||
}}
|
||||
icon={IconMoreH}
|
||||
size={'small'}
|
||||
/>
|
||||
{#if (object.comments ?? 0) > 0}
|
||||
<div class="step-lr75"><CommentsPresenter value={object} /></div>
|
||||
{/if}
|
||||
{#if (object.attachments ?? 0) > 0}
|
||||
<div class="step-lr75"><AttachmentsPresenter value={object} /></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem 1.25rem;
|
||||
background-color: rgba(222, 222, 240, 0.06);
|
||||
border-radius: 0.75rem;
|
||||
user-select: none;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
&.draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,10 +13,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import { getClient, UserBox } from '@anticrm/presentation'
|
||||
import { AttributeBarEditor, getClient, UserBox } from '@anticrm/presentation'
|
||||
import { Task } from '@anticrm/task'
|
||||
import task from '../plugin'
|
||||
|
||||
@ -29,13 +28,19 @@
|
||||
</script>
|
||||
|
||||
<div class="flex-between header">
|
||||
<UserBox _class={contact.class.Employee} title={task.string.TaskAssignee} caption='Assignee' bind:value={object.assignee} on:change={change} />
|
||||
<!-- <AttributeBarEditor key={'state'} {object} showHeader={false} /> -->
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
title={task.string.TaskAssignee}
|
||||
caption="Assignee"
|
||||
bind:value={object.assignee}
|
||||
on:change={change}
|
||||
/>
|
||||
<AttributeBarEditor key={'state'} {object} showHeader={false} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
width: 100%;
|
||||
padding: 0 .5rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -13,27 +13,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Task } from '@anticrm/task'
|
||||
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
|
||||
import EditTask from './EditTask.svelte'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import task from '../plugin'
|
||||
|
||||
import type { Task } from '@anticrm/task'
|
||||
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
|
||||
import EditTask from './EditTask.svelte'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import task from '../plugin'
|
||||
export let value: Task
|
||||
|
||||
export let value: Task
|
||||
|
||||
const client = getClient()
|
||||
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
|
||||
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPopup(EditTask, { _id: value._id }, 'full')
|
||||
}
|
||||
const client = getClient()
|
||||
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
|
||||
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPopup(EditTask, { _id: value._id }, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="sm-tool-icon" on:click={show}>
|
||||
<span class="icon"><Icon icon={task.icon.Task} size={'small'}/></span>{shortLabel}-{value.number}
|
||||
<span class="icon"><Icon icon={task.icon.Task} size={'small'} /></span>{shortLabel}-{value.number}
|
||||
</div>
|
||||
|
@ -13,16 +13,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill="var(--duotone-color)" d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0-0.1-0.1 c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1 C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h6h6 c2,0,3.6-1.6,3.6-3.6S20,6.4,18,6.4z"/>
|
||||
<path
|
||||
fill="var(--duotone-color)"
|
||||
d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0-0.1-0.1 c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1 C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h6h6 c2,0,3.6-1.6,3.6-3.6S20,6.4,18,6.4z"
|
||||
/>
|
||||
<g {fill}>
|
||||
<path d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0 c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1-0.1c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3 C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1c0,0,0,0,0,0C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0 c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h0.6l1.2-1.2H6c-1.3,0-2.4-1.1-2.4-2.4c0-1.3,1.1-2.4,2.4-2.4h0.1 c0.2,0,0.4,0,0.6,0c0.2,0,0.4-0.1,0.6-0.2c0.2-0.1,0.3-0.3,0.4-0.4c0.1-0.1,0.1-0.2,0.2-0.3C7.8,6.5,7.9,6.4,8,6.2l0,0 c0.7-1.5,2.2-2.6,4-2.6s3.3,1.1,4,2.6l0,0c0.1,0.2,0.1,0.3,0.2,0.4c0,0.1,0.1,0.2,0.2,0.3c0.1,0.2,0.2,0.3,0.4,0.4 c0.2,0.1,0.4,0.2,0.6,0.2c0.2,0,0.4,0,0.6,0H18c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4h-1.8l1.2,1.2H18c2,0,3.6-1.6,3.6-3.6 S20,6.4,18,6.4z"/>
|
||||
<path d="M12,11.2l-4.4,4.4l0.8,0.8l3-3V21c0,0.3,0.3,0.6,0.6,0.6s0.6-0.3,0.6-0.6v-7.6l3,3l0.8-0.8L12,11.2z"/>
|
||||
<path
|
||||
d="M18,6.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0-0.1-0.1c0,0,0,0,0,0c0,0,0,0,0,0 c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0-0.1-0.1c0-0.1-0.1-0.2-0.2-0.4c-0.9-1.9-2.8-3.3-5.1-3.3c-2.3,0-4.2,1.4-5.1,3.3 C6.8,5.9,6.7,6,6.7,6.1c0,0.1-0.1,0.1-0.1,0.1c0,0,0,0,0,0C6.6,6.3,6.6,6.3,6.5,6.3c0,0,0,0-0.1,0c0,0,0,0-0.1,0 c-0.1,0-0.2,0-0.3,0C4,6.4,2.4,8,2.4,10S4,13.6,6,13.6h0.6l1.2-1.2H6c-1.3,0-2.4-1.1-2.4-2.4c0-1.3,1.1-2.4,2.4-2.4h0.1 c0.2,0,0.4,0,0.6,0c0.2,0,0.4-0.1,0.6-0.2c0.2-0.1,0.3-0.3,0.4-0.4c0.1-0.1,0.1-0.2,0.2-0.3C7.8,6.5,7.9,6.4,8,6.2l0,0 c0.7-1.5,2.2-2.6,4-2.6s3.3,1.1,4,2.6l0,0c0.1,0.2,0.1,0.3,0.2,0.4c0,0.1,0.1,0.2,0.2,0.3c0.1,0.2,0.2,0.3,0.4,0.4 c0.2,0.1,0.4,0.2,0.6,0.2c0.2,0,0.4,0,0.6,0H18c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4h-1.8l1.2,1.2H18c2,0,3.6-1.6,3.6-3.6 S20,6.4,18,6.4z"
|
||||
/>
|
||||
<path d="M12,11.2l-4.4,4.4l0.8,0.8l3-3V21c0,0.3,0.3,0.6,0.6,0.6s0.6-0.3,0.6-0.6v-7.6l3,3l0.8-0.8L12,11.2z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
@ -19,11 +19,15 @@ import { Resources } from '@anticrm/platform'
|
||||
import CreateTask from './components/CreateTask.svelte'
|
||||
import CreateProject from './components/CreateProject.svelte'
|
||||
import TaskPresenter from './components/TaskPresenter.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
|
||||
export { createProjectKanban } from './utils'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
CreateTask,
|
||||
CreateProject,
|
||||
TaskPresenter
|
||||
TaskPresenter,
|
||||
KanbanCard
|
||||
}
|
||||
})
|
||||
|
@ -32,7 +32,8 @@ export default mergeIds(taskId, task, {
|
||||
TaskAssignee: '' as IntlString,
|
||||
TaskDescription: '' as IntlString,
|
||||
NoAttachmentsForTask: '' as IntlString,
|
||||
UploadDropFilesHere: '' as IntlString
|
||||
UploadDropFilesHere: '' as IntlString,
|
||||
More: '' as IntlString
|
||||
},
|
||||
status: {
|
||||
AssigneeRequired: '' as IntlString
|
||||
|
@ -14,9 +14,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Doc, Ref, Space } from '@anticrm/core'
|
||||
import type { Class, Data, Doc, Ref, Space, State } from '@anticrm/core'
|
||||
import login from '@anticrm/login'
|
||||
import { getMetadata } from '@anticrm/platform'
|
||||
import { Project } from '@anticrm/task'
|
||||
import core from '@anticrm/core'
|
||||
import view, { Kanban } from '@anticrm/view'
|
||||
|
||||
export async function uploadFile (space: Ref<Space>, file: File, attachedTo: Ref<Doc>): Promise<string> {
|
||||
console.log(file)
|
||||
@ -40,3 +43,41 @@ export async function uploadFile (space: Ref<Space>, file: File, attachedTo: Ref
|
||||
console.log(uuid)
|
||||
return uuid
|
||||
}
|
||||
|
||||
export async function createProjectKanban (
|
||||
projectId: Ref<Project>,
|
||||
factory: <T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, data: Data<T>, id: Ref<T>) => Promise<void>
|
||||
): Promise<void> {
|
||||
const states = [
|
||||
{ color: '#7C6FCD', name: 'Open' },
|
||||
{ color: '#6F7BC5', name: 'In Progress' },
|
||||
{ color: '#77C07B', name: 'Under review' },
|
||||
{ color: '#A5D179', name: 'Done' },
|
||||
{ color: '#F28469', name: 'Invalid' }
|
||||
]
|
||||
const ids: Array<Ref<State>> = []
|
||||
for (const st of states) {
|
||||
const sid = (projectId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
|
||||
await factory(
|
||||
core.class.State,
|
||||
projectId,
|
||||
{
|
||||
title: st.name,
|
||||
color: st.color
|
||||
},
|
||||
sid
|
||||
)
|
||||
ids.push(sid)
|
||||
}
|
||||
|
||||
await factory(
|
||||
view.class.Kanban,
|
||||
projectId,
|
||||
{
|
||||
attachedTo: projectId,
|
||||
states: ids,
|
||||
order: []
|
||||
},
|
||||
(projectId + '.kanban.') as Ref<Kanban>
|
||||
)
|
||||
}
|
||||
|
@ -1,59 +1,58 @@
|
||||
//
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
import type { AttachedDoc, Doc } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
|
||||
import StringEditor from './components/StringEditor.svelte'
|
||||
import StringPresenter from './components/StringPresenter.svelte'
|
||||
import BooleanEditor from './components/BooleanEditor.svelte'
|
||||
import BooleanPresenter from './components/BooleanPresenter.svelte'
|
||||
import StatePresenter from './components/StatePresenter.svelte'
|
||||
import StateEditor from './components/StateEditor.svelte'
|
||||
import TimestampPresenter from './components/TimestampPresenter.svelte'
|
||||
import DateEditor from './components/DateEditor.svelte'
|
||||
import DatePresenter from './components/DatePresenter.svelte'
|
||||
import TableView from './components/TableView.svelte'
|
||||
import Table from './components/Table.svelte'
|
||||
import KanbanView from './components/KanbanView.svelte'
|
||||
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import { getClient, MessageBox } from '@anticrm/presentation'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import {buildModel} from './utils'
|
||||
import BooleanEditor from './components/BooleanEditor.svelte'
|
||||
import BooleanPresenter from './components/BooleanPresenter.svelte'
|
||||
import DateEditor from './components/DateEditor.svelte'
|
||||
import DatePresenter from './components/DatePresenter.svelte'
|
||||
import KanbanView from './components/KanbanView.svelte'
|
||||
import StateEditor from './components/StateEditor.svelte'
|
||||
import StatePresenter from './components/StatePresenter.svelte'
|
||||
import StringEditor from './components/StringEditor.svelte'
|
||||
import StringPresenter from './components/StringPresenter.svelte'
|
||||
import Table from './components/Table.svelte'
|
||||
import TableView from './components/TableView.svelte'
|
||||
import TimestampPresenter from './components/TimestampPresenter.svelte'
|
||||
|
||||
export { default as ContextMenu } from './components/Menu.svelte'
|
||||
export { buildModel, getActions, getObjectPresenter } from './utils'
|
||||
export { Table }
|
||||
export { buildModel, getObjectPresenter, getActions } from './utils'
|
||||
|
||||
function Delete(object: Doc): void {
|
||||
function Delete (object: Doc): void {
|
||||
showPopup(MessageBox, {
|
||||
label: 'Delete object',
|
||||
message: 'Do you want to delete this object?'
|
||||
}, undefined, (result) => {
|
||||
if (result) {
|
||||
if (result !== undefined) {
|
||||
const client = getClient()
|
||||
if(client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
|
||||
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
|
||||
const adoc = object as AttachedDoc
|
||||
client.removeCollection(object._class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection)
|
||||
client.removeCollection(object._class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection).catch(err => console.error(err))
|
||||
} else {
|
||||
client.removeDoc(object._class, object.space, object._id)
|
||||
client.removeDoc(object._class, object.space, object._id).catch(err => console.error(err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default async () => ({
|
||||
export default async (): Promise<Resources> => ({
|
||||
actionImpl: {
|
||||
Delete
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user