Board: Update card (#1826)

Signed-off-by: Anna No <anna.no@xored.com>
This commit is contained in:
Anna No 2022-05-23 14:28:41 +07:00 committed by GitHub
parent 5f5102a939
commit e4b7575147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 289 additions and 765 deletions

View File

@ -156,7 +156,9 @@ async function genApplicant (
assignee: faker.random.arrayElement(emoloyeeIds), assignee: faker.random.arrayElement(emoloyeeIds),
state: faker.random.arrayElement(states), state: faker.random.arrayElement(states),
doneState: null, doneState: null,
rank: rank as string rank: rank as string,
startDate: null,
dueDate: null
} }
// Update or create candidate // Update or create candidate

View File

@ -251,7 +251,9 @@ async function createApplicant (
assignee: null, assignee: null,
state, state,
doneState: null, doneState: null,
rank: calcRank(lastOne, undefined) rank: calcRank(lastOne, undefined),
startDate: null,
dueDate: null
} }
// Update or create candidate // Update or create candidate

View File

@ -14,9 +14,9 @@
// //
// To help typescript locate view plugin properly // To help typescript locate view plugin properly
import type { Board, Card, CardDate, CardLabel, MenuPage, CommonBoardPreference } from '@anticrm/board' import type { Board, Card, CardLabel, MenuPage, CommonBoardPreference } from '@anticrm/board'
import type { Employee } from '@anticrm/contact' import type { Employee } from '@anticrm/contact'
import { DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp, Type } from '@anticrm/core' import { DOMAIN_MODEL, IndexKind, Markup, Ref } from '@anticrm/core'
import { import {
ArrOf, ArrOf,
Builder, Builder,
@ -33,7 +33,7 @@ import {
import attachment from '@anticrm/model-attachment' import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter' import chunter from '@anticrm/model-chunter'
import contact from '@anticrm/model-contact' import contact from '@anticrm/model-contact'
import core, { TAttachedDoc, TDoc, TObj } from '@anticrm/model-core' import core, { TAttachedDoc, TDoc } from '@anticrm/model-core'
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task' import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
import view, { actionTemplates, createAction } from '@anticrm/model-view' import view, { actionTemplates, createAction } from '@anticrm/model-view'
import workbench, { Application } from '@anticrm/model-workbench' import workbench, { Application } from '@anticrm/model-workbench'
@ -42,13 +42,6 @@ import type { AnyComponent } from '@anticrm/ui'
import preference, { TPreference } from '@anticrm/model-preference' import preference, { TPreference } from '@anticrm/model-preference'
import board from './plugin' import board from './plugin'
/**
* @public
*/
export function TypeCardDate (): Type<CardDate> {
return { _class: board.class.CardDate, label: board.string.Dates }
}
@Model(board.class.Board, task.class.SpaceWithStates) @Model(board.class.Board, task.class.SpaceWithStates)
@UX(board.string.Board, board.icon.Board) @UX(board.string.Board, board.icon.Board)
export class TBoard extends TSpaceWithStates implements Board { export class TBoard extends TSpaceWithStates implements Board {
@ -56,14 +49,6 @@ export class TBoard extends TSpaceWithStates implements Board {
background!: string background!: string
} }
@Model(board.class.CardDate, core.class.Obj, DOMAIN_MODEL)
@UX(board.string.Dates)
export class TCardDate extends TObj implements CardDate {
dueDate?: Timestamp
isChecked?: boolean
startDate?: Timestamp
}
@Model(board.class.CardLabel, core.class.AttachedDoc, DOMAIN_MODEL) @Model(board.class.CardLabel, core.class.AttachedDoc, DOMAIN_MODEL)
@UX(board.string.Labels) @UX(board.string.Labels)
export class TCardLabel extends TAttachedDoc implements CardLabel { export class TCardLabel extends TAttachedDoc implements CardLabel {
@ -90,9 +75,6 @@ export class TCard extends TTask implements Card {
@Prop(TypeBoolean(), board.string.IsArchived) @Prop(TypeBoolean(), board.string.IsArchived)
isArchived?: boolean isArchived?: boolean
@Prop(TypeCardDate(), board.string.Dates)
date?: CardDate
@Prop(TypeMarkup(), board.string.Description) @Prop(TypeMarkup(), board.string.Description)
@Index(IndexKind.FullText) @Index(IndexKind.FullText)
description!: Markup description!: Markup
@ -125,7 +107,7 @@ export class TMenuPage extends TDoc implements MenuPage {
} }
export function createModel (builder: Builder): void { export function createModel (builder: Builder): void {
builder.createModel(TBoard, TCard, TCardLabel, TCardDate, TMenuPage, TCommonBoardPreference) builder.createModel(TBoard, TCard, TCardLabel, TMenuPage, TCommonBoardPreference)
builder.createDoc(board.class.MenuPage, core.space.Model, { builder.createDoc(board.class.MenuPage, core.space.Model, {
component: board.component.Archive, component: board.component.Archive,
@ -228,10 +210,6 @@ export function createModel (builder: Builder): void {
presenter: board.component.CardLabelPresenter presenter: board.component.CardLabelPresenter
}) })
builder.mixin(board.class.CardDate, core.class.Class, view.mixin.AttributePresenter, {
presenter: board.component.CardDatePresenter
})
builder.mixin(board.class.Board, core.class.Class, view.mixin.AttributePresenter, { builder.mixin(board.class.Board, core.class.Class, view.mixin.AttributePresenter, {
presenter: board.component.BoardPresenter presenter: board.component.BoardPresenter
}) })
@ -269,6 +247,16 @@ export function createModel (builder: Builder): void {
board.viewlet.Table board.viewlet.Table
) )
builder.createDoc(
task.class.WonState,
core.space.Model,
{
title: board.string.Completed,
rank: '0'
},
board.state.Completed
)
// card actions // card actions
createAction( createAction(
builder, builder,

View File

@ -29,7 +29,6 @@ export default mergeIds(boardId, board, {
KanbanCard: '' as AnyComponent, KanbanCard: '' as AnyComponent,
CardPresenter: '' as AnyComponent, CardPresenter: '' as AnyComponent,
CardLabelPresenter: '' as AnyComponent, CardLabelPresenter: '' as AnyComponent,
CardDatePresenter: '' as AnyComponent,
BoardPresenter: '' as AnyComponent, BoardPresenter: '' as AnyComponent,
TemplatesIcon: '' as AnyComponent, TemplatesIcon: '' as AnyComponent,
Cards: '' as AnyComponent, Cards: '' as AnyComponent,

View File

@ -113,6 +113,12 @@ export class TTask extends TAttachedDoc implements Task {
// @Prop(TypeRef(contact.class.Employee), task.string.TaskAssignee) // @Prop(TypeRef(contact.class.Employee), task.string.TaskAssignee)
assignee!: Ref<Employee> | null assignee!: Ref<Employee> | null
@Prop(TypeDate(), task.string.DueDate)
dueDate!: Timestamp | null
@Prop(TypeDate(), task.string.StartDate)
startDate!: Timestamp | null
declare rank: string declare rank: string
@Prop(Collection(task.class.TodoItem), task.string.Todos) @Prop(Collection(task.class.TodoItem), task.string.Todos)

View File

@ -16,7 +16,7 @@
import type { Ref, Space } from '@anticrm/core' import type { Ref, Space } from '@anticrm/core'
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation' import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation'
import type { IntlString, Resource } from '@anticrm/platform' import type { Resource } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform' import { mergeIds } from '@anticrm/platform'
import { KanbanTemplate, taskId } from '@anticrm/task' import { KanbanTemplate, taskId } from '@anticrm/task'
import task from '@anticrm/task-resources/src/plugin' import task from '@anticrm/task-resources/src/plugin'
@ -58,31 +58,6 @@ export default mergeIds(taskId, task, {
StatusTableView: '' as AnyComponent, StatusTableView: '' as AnyComponent,
TaskHeader: '' as AnyComponent TaskHeader: '' as AnyComponent
}, },
string: {
TaskState: '' as IntlString,
TaskStateTitle: '' as IntlString,
TaskStateDone: '' as IntlString,
TaskNumber: '' as IntlString,
Todo: '' as IntlString,
TaskDone: '' as IntlString,
TaskDueTo: '' as IntlString,
TaskParent: '' as IntlString,
IssueName: '' as IntlString,
TaskComments: '' as IntlString,
TaskLabels: '' as IntlString,
StateTemplateTitle: '' as IntlString,
StateTemplateColor: '' as IntlString,
KanbanTemplateTitle: '' as IntlString,
Rank: '' as IntlString,
EditStates: '' as IntlString,
MarkAsDone: '' as IntlString,
MarkAsUndone: '' as IntlString,
Kanban: '' as IntlString,
ApplicationLabelTask: '' as IntlString,
Projects: '' as IntlString,
SearchTask: '' as IntlString,
ManageProjectStatues: '' as IntlString
},
space: { space: {
TasksPublic: '' as Ref<Space> TasksPublic: '' as Ref<Space>
}, },

View File

@ -451,6 +451,7 @@ input.search {
.w-9 { width: 2.25rem; } .w-9 { width: 2.25rem; }
.w-14 { width: 3.5rem; } .w-14 { width: 3.5rem; }
.w-16 { width: 4rem; } .w-16 { width: 4rem; }
.w-22 { width: 5.5rem; }
.w-24 { width: 6rem; } .w-24 { width: 6rem; }
.w-60 { width: 15rem; } .w-60 { width: 15rem; }
.w-85 { width: 21.25rem; } .w-85 { width: 21.25rem; }

View File

@ -7,7 +7,7 @@
"Minutes": "{minutes, plural, =0 {less than a minute ago} =1 {a minute ago} other {# minutes ago}}", "Minutes": "{minutes, plural, =0 {less than a minute ago} =1 {a minute ago} other {# minutes ago}}",
"Hours": "{hours, plural, =0 {less than an hour ago} =1 {an hour ago} other {# hours ago}}", "Hours": "{hours, plural, =0 {less than an hour ago} =1 {an hour ago} other {# hours ago}}",
"Days": "{days, plural, =0 {today} =1 {yesterday} other {# days ago}}", "Days": "{days, plural, =0 {today} =1 {yesterday} other {# days ago}}",
"Months": "{months, plural, =0 {this month} =1 {a month aago} other {# months ago}}", "Months": "{months, plural, =0 {this month} =1 {a month ago} other {# months ago}}",
"Years": "{years, plural, =0 {this year} =1 {a year ago} other {# years ago}}", "Years": "{years, plural, =0 {this year} =1 {a year ago} other {# years ago}}",
"ShowMore": "Show more", "ShowMore": "Show more",
"ShowLess": "Show less", "ShowLess": "Show less",

View File

@ -1,5 +1,6 @@
{ {
"string": { "string": {
"Completed": "Completed",
"Name": "Name", "Name": "Name",
"CreateBoard": "Create board", "CreateBoard": "Create board",
"OpenCard": "Open Card", "OpenCard": "Open Card",
@ -28,17 +29,14 @@
"IsArchived": "Archived", "IsArchived": "Archived",
"BoardCreateLabel": "Board", "BoardCreateLabel": "Board",
"Settings": "Settings", "Settings": "Settings",
"InList": "in list",
"Suggested": "Suggested", "Suggested": "Suggested",
"AddToCard": "Add to card",
"Labels": "Labels", "Labels": "Labels",
"CreateLabel": "Create a new label", "CreateLabel": "Create a new label",
"SearchLabels": "Search labels...", "SearchLabels": "Search labels...",
"SelectColor": "Select a color", "SelectColor": "Select a color",
"NoColor": "No color.", "NoColor": "No color.",
"NoColorInfo": "This won't show up on the front of cards.", "NoColorInfo": "This won't show up on the front of cards.",
"Checklist": "Checklist", "Checklists": "Checklists",
"AddChecklistItem": "Add an item",
"ChecklistDropdownNone": "(none)", "ChecklistDropdownNone": "(none)",
"ShowDoneChecklistItems": "Show checked items ({done})", "ShowDoneChecklistItems": "Show checked items ({done})",
"HideDoneChecklistItems": "Hide checked items", "HideDoneChecklistItems": "Hide checked items",
@ -80,8 +78,6 @@
"List": "List", "List": "List",
"Position": "Position", "Position": "Position",
"Current": "{label} (current)", "Current": "{label} (current)",
"StartDate": "Start date",
"DueDate": "Due date",
"Save": "Save", "Save": "Save",
"Remove": "Remove", "Remove": "Remove",
"NullDate": "M/D/YYYY", "NullDate": "M/D/YYYY",

View File

@ -1,5 +1,6 @@
{ {
"string": { "string": {
"Completed": "Завершено",
"Name": "Название", "Name": "Название",
"CreateBoard": "Создать", "CreateBoard": "Создать",
"OpenCard": "Открыть", "OpenCard": "Открыть",
@ -28,17 +29,14 @@
"IsArchived": "Архивировано", "IsArchived": "Архивировано",
"BoardCreateLabel": "Board", "BoardCreateLabel": "Board",
"Settings": "Настройки", "Settings": "Настройки",
"InList": "в списке",
"Suggested": "Предложенное", "Suggested": "Предложенное",
"AddToCard": "Добавить",
"Labels": "Метки", "Labels": "Метки",
"CreateLabel": "Создать", "CreateLabel": "Создать",
"SearchLabels": "Поиск...", "SearchLabels": "Поиск...",
"SelectColor": "Выберите цвет", "SelectColor": "Выберите цвет",
"NoColor": "Без цвета.", "NoColor": "Без цвета.",
"NoColorInfo": "Не будет показываться на доске.", "NoColorInfo": "Не будет показываться на доске.",
"Checklist": "Списки", "Checklists": "Списки",
"AddChecklistItem": "Добавить",
"ChecklistDropdownNone": "(не выбрано)", "ChecklistDropdownNone": "(не выбрано)",
"ShowDoneChecklistItems": "Показать отмеченные ({done})", "ShowDoneChecklistItems": "Показать отмеченные ({done})",
"HideDoneChecklistItems": "Скрыть отмеченные", "HideDoneChecklistItems": "Скрыть отмеченные",
@ -80,8 +78,6 @@
"List": "Список", "List": "Список",
"Position": "Позиция", "Position": "Позиция",
"Current": "{label} (текущий)", "Current": "{label} (текущий)",
"StartDate": "Начало",
"DueDate": "Срок",
"Save": "Сохранить", "Save": "Сохранить",
"Remove": "Удалить", "Remove": "Удалить",
"NullDate": "М/Д/ГГГГ", "NullDate": "М/Д/ГГГГ",

View File

@ -66,7 +66,9 @@
description: '', description: '',
members: [], members: [],
labels: [], labels: [],
location: '' location: '',
startDate: null,
dueDate: null
} }
await client.addCollection(board.class.Card, _space, space, board.class.Board, 'cards', value, cardId) await client.addCollection(board.class.Card, _space, space, board.class.Board, 'cards', value, cardId)

View File

@ -14,23 +14,24 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Attachments } from '@anticrm/attachment-resources'
import type { Card } from '@anticrm/board' import type { Card } from '@anticrm/board'
import { Class, Ref } from '@anticrm/core' import core, { Class, Ref, Space } from '@anticrm/core'
import { Panel } from '@anticrm/panel' import { Panel } from '@anticrm/panel'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import type { State, TodoItem } from '@anticrm/task' import type { State, TodoItem } from '@anticrm/task'
import task from '@anticrm/task' import task from '@anticrm/task'
import { StyledTextBox } from '@anticrm/text-editor' import { StyledTextBox } from '@anticrm/text-editor'
import { Button, EditBox, Icon, Label } from '@anticrm/ui' import { Button, CircleButton, EditBox, IconAdd, IconMoreH, Label, showPopup } from '@anticrm/ui'
import { invokeAction, UpDownNavigator } from '@anticrm/view-resources' import { ContextMenu, invokeAction, UpDownNavigator } from '@anticrm/view-resources'
import { createEventDispatcher, onMount } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import board from '../plugin' import board from '../plugin'
import { getCardActions } from '../utils/CardActionUtils' import { getCardActions } from '../utils/CardActionUtils'
import { updateCard } from '../utils/CardUtils' import { updateCard } from '../utils/CardUtils'
import { getPopupAlignment } from '../utils/PopupUtils'
import CardActions from './editor/CardActions.svelte' import CardActions from './editor/CardActions.svelte'
import CardAttachments from './editor/CardAttachments.svelte'
import CardChecklist from './editor/CardChecklist.svelte' import CardChecklist from './editor/CardChecklist.svelte'
import CardDetails from './editor/CardDetails.svelte' import AddChecklist from './popups/AddChecklist.svelte'
export let _id: Ref<Card> export let _id: Ref<Card>
export let _class: Ref<Class<Card>> export let _class: Ref<Class<Card>>
@ -38,10 +39,12 @@
const client = getClient() const client = getClient()
const cardQuery = createQuery() const cardQuery = createQuery()
const stateQuery = createQuery() const stateQuery = createQuery()
const spaceQuery = createQuery()
const checklistsQuery = createQuery() const checklistsQuery = createQuery()
let object: Card | undefined let object: Card | undefined
let state: State | undefined let state: State | undefined
let space: Space | undefined
let handleMove: (e: Event) => void let handleMove: (e: Event) => void
let checklists: TodoItem[] = [] let checklists: TodoItem[] = []
@ -51,6 +54,10 @@
} }
} }
function addChecklist (e: Event) {
showPopup(AddChecklist, { value: object }, getPopupAlignment(e))
}
$: cardQuery.query(_class, { _id }, (result) => { $: cardQuery.query(_class, { _id }, (result) => {
object = result[0] object = result[0]
}) })
@ -60,6 +67,11 @@
state = result[0] state = result[0]
}) })
$: object?.space &&
spaceQuery.query(core.class.Space, { _id: object.space }, (result) => {
space = result[0]
})
$: object && $: object &&
checklistsQuery.query(task.class.TodoItem, { space: object.space, attachedTo: object._id }, (result) => { checklistsQuery.query(task.class.TodoItem, { space: object.space, attachedTo: object._id }, (result) => {
checklists = result checklists = result
@ -86,8 +98,9 @@
icon={board.icon.Card} icon={board.icon.Card}
title={object?.title} title={object?.title}
{object} {object}
isHeader={false} isHeader
isAside={true} isAside={true}
isSub={false}
isFullSize isFullSize
on:fullsize on:fullsize
on:close={() => dispatch('close')} on:close={() => dispatch('close')}
@ -95,40 +108,33 @@
<svelte:fragment slot="navigator"> <svelte:fragment slot="navigator">
<UpDownNavigator element={object} /> <UpDownNavigator element={object} />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="header">
<!-- TODO cover --> <div class="flex fs-title flex-gap-1">
<div class="flex-row-stretch"> <span class="over-underline" on:click={handleMove}>{space?.name}</span>><span
<div class="w-9"> class="over-underline"
<Icon icon={board.icon.Card} size="large" /> on:click={handleMove}>{state?.title}</span
>
</div> </div>
<div class="fs-title text-lg"> </svelte:fragment>
<svelte:fragment slot="tools">
<Button
icon={IconMoreH}
kind="transparent"
size="medium"
on:click={(e) => {
showPopup(ContextMenu, { object }, getPopupAlignment(e))
}}
/>
</svelte:fragment>
<div class="flex-row-stretch">
<div class="fs-title text-xl">
<EditBox bind:value={object.title} maxWidth="39rem" focus on:change={() => change('title', object?.title)} /> <EditBox bind:value={object.title} maxWidth="39rem" focus on:change={() => change('title', object?.title)} />
</div> </div>
</div> </div>
<div class="flex-row-stretch">
<div class="w-9" />
<div>
<Label label={board.string.InList} />
<span class="state-name ml-1" on:click={handleMove}>{state?.title}</span>
</div>
</div>
<div class="flex-row-stretch"> <div class="flex-row-stretch">
<div class="flex-grow mr-4"> <div class="flex-grow mr-4">
<div class="flex-row-stretch"> <div class="flex-row-stretch">
<div class="w-9" /> <div class="background-bg-accent border-bg-accent border-radius-3 p-2 mt-2 w-full">
<CardDetails bind:value={object} />
</div>
<div class="flex-row-stretch mt-4 mb-2">
<div class="w-9">
<Icon icon={board.icon.Card} size="large" />
</div>
<div class="fs-title">
<Label label={board.string.Description} />
</div>
</div>
<div class="flex-row-stretch">
<div class="w-9" />
<div class="background-bg-accent border-bg-accent border-radius-3 p-2 w-full">
<StyledTextBox <StyledTextBox
alwaysEdit={true} alwaysEdit={true}
showButtons={false} showButtons={false}
@ -138,13 +144,22 @@
/> />
</div> </div>
</div> </div>
<CardAttachments value={object} /> <div class="mt-6">
{#each checklists as checklist} <Attachments objectId={_id} {_class} space={object.space} attachments={object.attachments ?? 0} />
<CardChecklist value={checklist} /> </div>
{/each} <div class="flex-row-center mt-6">
<span class="text-xl font-medium caption-color mr-3"><Label label={board.string.Checklists} /></span>
<CircleButton icon={IconAdd} size="small" selected on:click={addChecklist} />
</div>
<div class="mr-2 ml-2 mb-4">
{#each checklists as checklist}
<CardChecklist value={checklist} />
{/each}
</div>
</div> </div>
</div> </div>
<span slot="actions-label"><Label label={board.string.Card} /></span>
<span slot="actions" />
<svelte:fragment slot="custom-attributes" let:direction> <svelte:fragment slot="custom-attributes" let:direction>
{#if direction === 'column'} {#if direction === 'column'}
<CardActions bind:value={object} /> <CardActions bind:value={object} />
@ -154,13 +169,3 @@
</svelte:fragment> </svelte:fragment>
</Panel> </Panel>
{/if} {/if}
<style lang="scss">
.state-name {
text-decoration: underline;
&:hover {
color: var(--caption-color);
}
}
</style>

View File

@ -15,7 +15,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { AttachmentDroppable, AttachmentsPresenter } from '@anticrm/attachment-resources' import { AttachmentDroppable, AttachmentsPresenter } from '@anticrm/attachment-resources'
import type { Card, CardDate } from '@anticrm/board' import type { Card } from '@anticrm/board'
import { CommentsPresenter } from '@anticrm/chunter-resources' import { CommentsPresenter } from '@anticrm/chunter-resources'
import contact, { Employee } from '@anticrm/contact' import contact, { Employee } from '@anticrm/contact'
import type { Ref, WithLookup } from '@anticrm/core' import type { Ref, WithLookup } from '@anticrm/core'
@ -62,9 +62,6 @@
updateCardMembers(object, client, e.detail) updateCardMembers(object, client, e.detail)
} }
function updateDate (e: CustomEvent<CardDate>) {
client.update(object, { date: e.detail })
}
$: coverBackground = object.cover?.color ? `background-color: ${numberToHexColor(object.cover.color)}` : '' $: coverBackground = object.cover?.color ? `background-color: ${numberToHexColor(object.cover.color)}` : ''
</script> </script>
@ -178,9 +175,9 @@
<div class="float-left"> <div class="float-left">
<NotificationPresenter {object} /> <NotificationPresenter {object} />
</div> </div>
{#if object.date && hasDate(object)} {#if hasDate(object)}
<div class="float-left"> <div class="float-left">
<DatePresenter value={object.date} size="x-small" on:update={updateDate} /> <DatePresenter value={object} size="x-small" />
</div> </div>
{/if} {/if}
{#if object.description} {#if object.description}

View File

@ -24,7 +24,8 @@
'title', 'title',
'$lookup.state', '$lookup.state',
{ key: '', presenter: board.component.CardLabels, label: board.string.Labels }, { key: '', presenter: board.component.CardLabels, label: board.string.Labels },
'date', 'startDate',
'dueDate',
{ key: 'members', presenter: board.component.UserBoxList, label: board.string.Members, sortingKey: '' }, { key: 'members', presenter: board.component.UserBoxList, label: board.string.Members, sortingKey: '' },
'modifiedOn' 'modifiedOn'
]} ]}

View File

@ -8,4 +8,4 @@
export let value: Ref<Employee>[] export let value: Ref<Employee>[]
</script> </script>
<UserBoxList items={value} _class={contact.class.Employee} label={board.string.Members} /> <UserBoxList items={value} _class={contact.class.Employee} label={board.string.Members} on:update />

View File

@ -53,7 +53,9 @@
description: '', description: '',
members: [], members: [],
labels: [], labels: [],
location: '' location: '',
startDate: null,
dueDate: null
} }
return client.addCollection(board.class.Card, space, space, board.class.Board, 'cards', value, newCardId) return client.addCollection(board.class.Card, space, space, board.class.Board, 'cards', value, newCardId)

View File

@ -15,78 +15,101 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Card } from '@anticrm/board' import type { Card } from '@anticrm/board'
import board from '@anticrm/board'
import { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
import { Button, IconAttachment, Label, showPopup } from '@anticrm/ui' import ui, { Button, CheckBox, DateRangePresenter, Label, IconAdd } from '@anticrm/ui'
import type { Action } from '@anticrm/view' import { invokeAction } from '@anticrm/view-resources'
import { getActions, invokeAction } from '@anticrm/view-resources'
import AddChecklist from '../popups/AddChecklist.svelte'
import AttachmentPicker from '../popups/AttachmentPicker.svelte'
import plugin from '../../plugin' import plugin from '../../plugin'
import { getPopupAlignment } from '../../utils/PopupUtils' import { getCardActions } from '../../utils/CardActionUtils'
import { updateCardMembers } from '../../utils/CardUtils'
import ColorPresenter from '../presenters/ColorPresenter.svelte'
import UserBoxList from '../UserBoxList.svelte'
import CardLabels from './CardLabels.svelte'
export let value: Card export let value: Card
const client = getClient() const client = getClient()
let topActions: Action[] = [] let coverHandler: (e: Event) => void
let toolsActions: Action[] = []
async function fetch () { function updateMembers (e: CustomEvent<Ref<Employee>[]>) {
const result = await getActions(client, value, value._class) updateCardMembers(value, client, e.detail)
topActions = result.filter((action) => action.context.group === 'top')
toolsActions = result.filter((action) => action.context.group !== 'top')
} }
fetch() function updateState (e: CustomEvent<boolean>) {
$: value.members && fetch() if (e.detail) {
$: value.isArchived && fetch() client.update(value, { doneState: board.state.Completed })
$: !value.isArchived && fetch() } else {
client.update(value, { doneState: null })
}
}
getCardActions(client, {
_id: { $in: [board.action.Cover] }
}).then(async (result) => {
for (const action of result) {
if (action._id === board.action.Cover) {
coverHandler = (e: Event) => invokeAction(value, e, action.action, action.actionProps)
}
}
})
</script> </script>
{#if value} {#if value}
<div class="flex-col flex-gap-3"> <div class="flex-col flex-gap-3 mt-4">
<div class="flex-col flex-gap-1"> <div class="flex-row-stretch flex-gap-1 items-center">
<Label label={plugin.string.AddToCard} /> <div class="label w-24">
{#each topActions as action} <Label label={plugin.string.Completed} />
<Button </div>
icon={action.icon} <CheckBox checked={value.doneState === board.state.Completed} on:value={updateState} />
label={action.label} </div>
kind="no-border" <div class="flex-row-stretch flex-gap-1 items-center">
justify="left" <div class="label w-24">
on:click={(e) => { <Label label={plugin.string.Members} />
invokeAction(value, e, action.action, action.actionProps) </div>
}} <UserBoxList value={value.members ?? []} on:update={updateMembers} />
/> </div>
{/each} <div class="flex-row-stretch flex-gap-1 items-center">
<Button <div class="label w-24">
icon={plugin.icon.Card} <Label label={plugin.string.Labels} />
label={plugin.string.Checklist} </div>
kind="no-border" <CardLabels {value} />
justify="left" </div>
on:click={(e) => { <div class="flex-row-stretch flex-gap-1 items-center">
showPopup(AddChecklist, { value }, getPopupAlignment(e)) <div class="label w-24">
}} <Label label={ui.string.StartDate} />
/> </div>
<Button <DateRangePresenter
icon={IconAttachment} value={value.startDate}
label={plugin.string.Attachments} editable={true}
kind="no-border" withTime={false}
justify="left" on:change={(e) => {
on:click={(e) => { console.log(e)
showPopup(AttachmentPicker, { value }, getPopupAlignment(e)) client.update(value, { startDate: e.detail })
}} }}
/> />
</div> </div>
<div class="flex-col flex-gap-1"> <div class="flex-row-stretch flex-gap-1 items-center">
<Label label={plugin.string.Actions} /> <div class="label w-24">
{#each toolsActions as action} <Label label={ui.string.DueDate} />
<Button </div>
icon={action.icon} <DateRangePresenter
label={action.label} value={value.dueDate}
kind="no-border" editable={true}
justify="left" withTime={false}
on:click={(e) => { on:change={(e) => client.update(value, { dueDate: e.detail })}
invokeAction(value, e, action.action, action.actionProps) />
}} </div>
/> <div class="flex-row-stretch flex-gap-1 items-center">
{/each} <div class="label w-24">
<Label label={plugin.string.Cover} />
</div>
{#if !value.cover?.color}
<Button icon={IconAdd} kind="no-border" on:click={coverHandler} />
{:else}
<ColorPresenter value={value.cover.color} on:click={coverHandler} />
{/if}
</div> </div>
</div> </div>
{/if} {/if}

View File

@ -1,63 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 attachment, { Attachment } from '@anticrm/attachment'
import type { Card } from '@anticrm/board'
import { getClient } from '@anticrm/presentation'
import { Button, Icon, IconAttachment, Label, showPopup } from '@anticrm/ui'
import AttachmentPicker from '../popups/AttachmentPicker.svelte'
import AttachmentPresenter from '../presenters/AttachmentPresenter.svelte'
import board from '../../plugin'
import { getPopupAlignment } from '../../utils/PopupUtils'
export let value: Card
const client = getClient()
let attachments: Attachment[] = []
async function fetch () {
attachments = await client.findAll(attachment.class.Attachment, { space: value.space, attachedTo: value._id })
}
function addAttachment (e: Event) {
showPopup(AttachmentPicker, { object: value }, getPopupAlignment(e))
}
$: value?.attachments && value.attachments > 0 && fetch()
</script>
{#if value !== undefined && value.attachments !== undefined && value.attachments > 0}
<div class="flex-col w-full">
<div class="flex-row-stretch mt-4 mb-2">
<div class="w-9">
<Icon icon={IconAttachment} size="large" />
</div>
<div class="flex-grow fs-title">
<Label label={board.string.Attachments} />
</div>
</div>
<div class="flex-row-stretch">
<div class="w-9" />
<div class="flex-col flex-gap-1 w-full">
{#each attachments as attach}
<AttachmentPresenter value={attach} />
{/each}
<div class="mt-2">
<Button label={board.string.AddAttachment} kind="no-border" on:click={addAttachment} />
</div>
</div>
</div>
</div>
{/if}

View File

@ -21,7 +21,8 @@
Button, Button,
CheckBox, CheckBox,
TextAreaEditor, TextAreaEditor,
Icon, IconAdd,
IconDelete,
IconMoreH, IconMoreH,
Progress, Progress,
showPopup, showPopup,
@ -131,9 +132,6 @@
{#if value !== undefined} {#if value !== undefined}
<div class="flex-col w-full"> <div class="flex-col w-full">
<div class="flex-row-stretch mt-4 mb-2"> <div class="flex-row-stretch mt-4 mb-2">
<div class="w-9">
<Icon icon={board.icon.Card} size="large" />
</div>
{#if isEditingName} {#if isEditingName}
<div class="flex-grow"> <div class="flex-grow">
<TextAreaEditor <TextAreaEditor
@ -154,19 +152,18 @@
{value.name} {value.name}
</div> </div>
{#if done > 0} {#if done > 0}
<div class="mr-1"> <Button
<Button label={hideDoneItems ? board.string.ShowDoneChecklistItems : board.string.HideDoneChecklistItems}
label={hideDoneItems ? board.string.ShowDoneChecklistItems : board.string.HideDoneChecklistItems} labelParams={{ done }}
labelParams={{ done }} kind="transparent"
kind="no-border" size="small"
size="small" on:click={() => {
on:click={() => { hideDoneItems = !hideDoneItems
hideDoneItems = !hideDoneItems }}
}} />
/>
</div>
{/if} {/if}
<Button label={board.string.Delete} kind="no-border" size="small" on:click={deleteChecklist} /> <Button icon={IconAdd} kind="transparent" size="small" on:click={startAddingItem} />
<Button icon={IconDelete} kind="transparent" size="small" on:click={deleteChecklist} />
{/if} {/if}
</div> </div>
<div class="flex-row-stretch mb-2 mt-1"> <div class="flex-row-stretch mb-2 mt-1">
@ -254,8 +251,6 @@
}} }}
/> />
</div> </div>
{:else}
<Button label={board.string.AddChecklistItem} kind="no-border" size="small" on:click={startAddingItem} />
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -1,122 +0,0 @@
<!--
// 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 type { Card, CardDate } from '@anticrm/board'
import contact, { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { createQuery, getClient, UsersPopup } from '@anticrm/presentation'
import { Button, IconAdd, Label, showPopup } from '@anticrm/ui'
import { invokeAction } from '@anticrm/view-resources'
import board from '../../plugin'
import { getCardActions } from '../../utils/CardActionUtils'
import { hasDate, updateCardMembers } from '../../utils/CardUtils'
import { getPopupAlignment } from '../../utils/PopupUtils'
import DatePresenter from '../presenters/DatePresenter.svelte'
import MemberPresenter from '../presenters/MemberPresenter.svelte'
import CardLabels from './CardLabels.svelte'
export let value: Card
const query = createQuery()
const client = getClient()
let members: Employee[] = []
const membersHandler = (e?: Event) => {
showPopup(
UsersPopup,
{
_class: contact.class.Employee,
multiSelect: true,
allowDeselect: true,
selectedUsers: members?.map((m) => m._id) ?? [],
placeholder: board.string.SearchMembers
},
getPopupAlignment(e),
undefined,
(result: Array<Ref<Employee>>) => {
updateCardMembers(value, client, result)
}
)
}
let dateHandler: (e: Event) => void
$: membersIds = members?.map((m) => m._id) ?? []
const getMenuItems = (member: Employee) => {
return [
[
{
title: board.string.RemoveFromCard,
handler: () => {
const newMembers = membersIds.filter((m) => m !== member._id)
updateCardMembers(value, client, newMembers)
}
}
]
]
}
$: query.query(contact.class.Employee, { _id: { $in: value.members } }, (result) => {
members = result
})
function updateDate (e: CustomEvent<CardDate>) {
client.update(value, { date: e.detail })
}
getCardActions(client, {
_id: { $in: [board.action.Dates] }
}).then(async (result) => {
for (const action of result) {
if (action._id === board.action.Dates) {
dateHandler = (e: Event) => invokeAction(value, e, action.action, action.actionProps)
}
}
})
</script>
{#if value}
{#if members && members.length > 0}
<div class="flex-col mt-4 mr-6">
<div class="text-md font-medium">
<Label label={board.string.Members} />
</div>
<div class="flex-row-center flex-gap-1">
{#each members as member}
<MemberPresenter value={member} size="large" menuItems={getMenuItems(member)} />
{/each}
<Button icon={IconAdd} shape="circle" kind="no-border" size="large" on:click={membersHandler} />
</div>
</div>
{/if}
{#if value.labels && value.labels.length > 0}
<div class="flex-col mt-4 mr-6">
<div class="text-md font-medium">
<Label label={board.string.Labels} />
</div>
<CardLabels {value} />
</div>
{/if}
{#if value.date && hasDate(value)}
<div class="flex-col mt-4">
<div class="text-md font-medium">
<Label label={board.string.Dates} />
</div>
{#key value.date}
<DatePresenter value={value.date} on:click={dateHandler} on:update={updateDate} />
{/key}
</div>
{/if}
{/if}

View File

@ -68,7 +68,7 @@
} }
</script> </script>
{#if labels && labels.length > 0} {#if labels}
<div <div
class="flex-row-center flex-wrap flex-gap-1 mb-1" class="flex-row-center flex-wrap flex-gap-1 mb-1"
class:labels-inline-container={isInline} class:labels-inline-container={isInline}

View File

@ -106,7 +106,7 @@
<div class="antiPopup w-85"> <div class="antiPopup w-85">
<div class="relative flex-row-center w-full "> <div class="relative flex-row-center w-full ">
<div class="flex-center flex-grow fs-title mt-1 mb-1"> <div class="flex-center flex-grow fs-title mt-1 mb-1">
<Label label={board.string.Checklist} /> <Label label={board.string.Checklists} />
</div> </div>
<div class="absolute mr-1 mt-1 mb-1" style:top="0" style:right="0"> <div class="absolute mr-1 mt-1 mb-1" style:top="0" style:right="0">

View File

@ -52,7 +52,9 @@
description: '', description: '',
members: [], members: [],
location: '', location: '',
labels: labels ?? [] labels: labels ?? [],
startDate: value.startDate,
dueDate: value.dueDate
} }
await client.addCollection( await client.addCollection(

View File

@ -1,35 +1,37 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { Card } from '@anticrm/board'
import { Label, Button, DateRangePresenter, CheckBox, Component } from '@anticrm/ui'
import { Card, CardDate } from '@anticrm/board'
import calendar from '@anticrm/calendar' import calendar from '@anticrm/calendar'
import { DocumentUpdate } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import task from '@anticrm/task'
import { Label, Button, DateRangePresenter, Component } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import board from '../../plugin' import board from '../../plugin'
import { getClient } from '@anticrm/presentation'
export let value: Card export let value: Card
const client = getClient() const client = getClient()
const query = createQuery()
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let startDate = value.date?.startDate let startDate = value.startDate
let savedStartDate = value.date?.startDate ?? Date.now() let dueDate = value.dueDate
let startDateEnabled = startDate !== undefined
$: startDate && (savedStartDate = startDate)
let dueDate = value.date?.dueDate
let savedDueDate = value.date?.dueDate ?? Date.now()
let dueDateEnabled = dueDate !== undefined
$: dueDate && (savedDueDate = dueDate)
function getEmptyDate (): CardDate {
return { _class: value.date?._class ?? board.class.CardDate }
}
function update () { function update () {
const date: CardDate = getEmptyDate() const date: DocumentUpdate<Card> = {}
if (startDate !== undefined) date.startDate = startDate if (startDate !== undefined) date.startDate = startDate
if (dueDate !== undefined) date.dueDate = dueDate if (dueDate !== undefined) date.dueDate = dueDate
client.update(value, { date }) client.update(value, date)
} }
$: value?._id &&
query.query(board.class.Card, { _id: value._id }, (result) => {
if (result?.[0]) {
startDate = result[0].startDate
dueDate = result[0].dueDate
}
})
</script> </script>
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle antiPopup-withCategory w-85"> <div class="antiPopup antiPopup-withHeader antiPopup-withTitle antiPopup-withCategory w-85">
@ -39,35 +41,19 @@
</div> </div>
<div class="ap-space bottom-divider" /> <div class="ap-space bottom-divider" />
<div class="ap-category"> <div class="ap-category">
<div class="categoryItem flex-center whitespace-nowrap"> <div class="categoryItem flex-center whitespace-nowrap w-22">
<Label label={board.string.StartDate} /> <Label label={task.string.StartDate} />
</div>
<div class="categoryItem p-2 flex-center">
<CheckBox
bind:checked={startDateEnabled}
on:value={() => {
startDate = startDateEnabled ? savedStartDate : undefined
}}
/>
</div> </div>
<div class="categoryItem w-full p-2"> <div class="categoryItem w-full p-2">
<DateRangePresenter bind:value={startDate} editable={startDateEnabled} labelNull={board.string.NullDate} /> <DateRangePresenter bind:value={startDate} editable={true} labelNull={board.string.NullDate} />
</div> </div>
</div> </div>
<div class="ap-category"> <div class="ap-category">
<div class="categoryItem flex-center whitespace-nowrap"> <div class="categoryItem flex-center whitespace-nowrap w-22">
<Label label={board.string.DueDate} /> <Label label={task.string.DueDate} />
</div>
<div class="categoryItem p-2 flex-center">
<CheckBox
bind:checked={dueDateEnabled}
on:value={() => {
dueDate = dueDateEnabled ? savedDueDate : undefined
}}
/>
</div> </div>
<div class="categoryItem w-full p-2"> <div class="categoryItem w-full p-2">
<DateRangePresenter bind:value={dueDate} editable={dueDateEnabled} labelNull={board.string.NullDate} /> <DateRangePresenter bind:value={dueDate} editable={true} labelNull={board.string.NullDate} />
</div> </div>
</div> </div>
<div class="ap-footer"> <div class="ap-footer">
@ -81,9 +67,10 @@
<Button <Button
label={board.string.Remove} label={board.string.Remove}
size={'small'} size={'small'}
on:click={() => { on:click={async () => {
client.update(value, { date: getEmptyDate() }) await client.update(value, { startDate: null, dueDate: null })
dispatch('close') startDate = null
dueDate = null
}} }}
/> />
<Button <Button

View File

@ -1,50 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Label, Button, ActionIcon, IconClose, EditBox } from '@anticrm/ui'
import board from '../../plugin'
import { getClient } from '@anticrm/presentation'
import { Attachment } from '@anticrm/attachment'
export let object: Attachment
let { name } = object
const client = getClient()
const dispatch = createEventDispatcher()
</script>
<div class="antiPopup antiPopup-withHeader antiPopup-withCategory w-85">
<div class="ap-space" />
<div class="flex-row-center header">
<div class="flex-center flex-grow">
<Label label={board.string.Edit} />
</div>
<div class="close-icon mr-1">
<ActionIcon
icon={IconClose}
size={'small'}
action={() => {
dispatch('close')
}}
/>
</div>
</div>
<div class="ap-space bottom-divider" />
<div class="ap-category">
<div class="ap-categoryItem">
<EditBox bind:value={name} maxWidth="18rem" label={board.string.LinkName} placeholder={board.string.LinkName} />
</div>
</div>
<div class="ap-footer">
<Button
size={'small'}
label={board.string.Update}
kind={'primary'}
on:click={() => {
if (!name) return
client.update(object, { name })
dispatch('close')
}}
/>
</div>
</div>

View File

@ -1,46 +0,0 @@
<script lang="ts">
import type { Employee } from '@anticrm/contact'
import type { IntlString } from '@anticrm/platform'
import { ActionIcon, IconClose, Label } from '@anticrm/ui'
import { ContactPresenter } from '@anticrm/contact-resources'
export let member: Employee
export let menuItems: { title: IntlString; handler: () => void }[][]
export let onClose: () => void
</script>
<div class="antiPopup container w-85">
<div class="absolute pt-3 pr-3" style:top="0" style:right="0">
<ActionIcon icon={IconClose} size={'small'} action={onClose} />
</div>
<div class="flex p-3">
<ContactPresenter value={member} />
</div>
{#if menuItems && menuItems.length > 0}
{#each menuItems as menuSubgroup, i}
{#each menuSubgroup as menuItem}
<div
class="menu-item pr-3 pl-3 pt-2 pb-2"
on:click={() => {
menuItem.handler()
onClose()
}}
>
<Label label={menuItem.title} />
</div>
{/each}
{#if i + 1 < menuItems.length}
<div class="bottom-divider ml-3 mr-3 mt-2 mb-2" />
{/if}
{/each}
{/if}
</div>
<style lang="scss">
.menu-item {
&:hover {
cursor: pointer;
background-color: var(--popup-bg-hover);
}
}
</style>

View File

@ -1,46 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Label, Button, ActionIcon, IconClose } from '@anticrm/ui'
import board from '../../plugin'
import { getClient } from '@anticrm/presentation'
import { Attachment } from '@anticrm/attachment'
export let object: Attachment
const client = getClient()
const dispatch = createEventDispatcher()
</script>
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle antiPopup-withCategory w-85">
<div class="ap-space" />
<div class="flex-row-center header">
<div class="flex-center flex-grow">
<Label label={board.string.Delete} />
</div>
<div class="close-icon mr-1">
<ActionIcon
icon={IconClose}
size={'small'}
action={() => {
dispatch('close')
}}
/>
</div>
</div>
<div class="ap-space bottom-divider" />
<div class="ap-box ml-4 mr-4 mt-4">
<Label label={board.string.DeleteAttachment} />
</div>
<div class="ap-footer">
<Button
size={'small'}
width="100%"
label={board.string.Delete}
kind={'dangerous'}
on:click={() => {
client.remove(object)
dispatch('close')
}}
/>
</div>
</div>

View File

@ -1,109 +0,0 @@
<!--
// 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 type { Attachment } from '@anticrm/attachment'
import { PDFViewer, getFileUrl } from '@anticrm/presentation'
import { Button, showPopup, TimeSince, closeTooltip } from '@anticrm/ui'
import board from '../../plugin'
import { getPopupAlignment } from '../../utils/PopupUtils'
import EditAttachment from '../popups/EditAttachment.svelte'
import RemoveAttachment from '../popups/RemoveAttachment.svelte'
export let value: Attachment
const maxLenght: number = 30
const trimFilename = (fname: string): string =>
fname.length > maxLenght
? fname.substring(0, (maxLenght - 1) / 2) + '...' + fname.substring(-(maxLenght - 1) / 2)
: fname
function iconLabel (name: string): string {
const parts = name.split('.')
const ext = parts[parts.length - 1]
return ext.substring(0, 4).toUpperCase()
}
function openEmbedded (contentType: string) {
return contentType.includes('application/pdf') || contentType.startsWith('image/')
}
function showPreview (contentType: string) {
return contentType.startsWith('image/')
}
function handleClick () {
closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'float')
}
</script>
<div class="flex-row-center">
{#if openEmbedded(value.type)}
<div class="flex-center cursor-pointer icon" on:click={handleClick}>
{#if showPreview(value.type)}
<img src={getFileUrl(value.file)} alt={value.name} />
{:else}
{iconLabel(value.name)}
{/if}
</div>
{:else}
<a class="no-line" href={getFileUrl(value.file)} download={value.name}>
<div class="flex-center icon">
{iconLabel(value.name)}
</div>
</a>
{/if}
<div class="flex-col-centre info">
<div class="fs-title">{trimFilename(value.name)}</div>
<div class="flex-row-center flex-gap-1">
<TimeSince value={value.lastModified} />
<Button
label={board.string.Edit}
on:click={(e) => {
showPopup(EditAttachment, { object: value }, getPopupAlignment(e))
}}
kind="transparent"
/>
<Button
label={board.string.Delete}
on:click={(e) => {
showPopup(RemoveAttachment, { object: value }, getPopupAlignment(e))
}}
kind="transparent"
/>
</div>
</div>
</div>
<style lang="scss">
.icon {
flex-shrink: 0;
margin-right: 1rem;
width: 8rem;
height: 6rem;
font-weight: 500;
font-size: 1rem;
color: var(--primary-button-color);
background-color: var(--grayscale-grey-03);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
overflow: hidden;
img {
max-width: 8rem;
max-height: 6rem;
}
}
</style>

View File

@ -1,26 +1,15 @@
<script lang="ts"> <script lang="ts">
import type { CardDate } from '@anticrm/board' import type { Card } from '@anticrm/board'
import { CheckBox, DatePresenter } from '@anticrm/ui' import { DatePresenter } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
export let value: CardDate export let value: Card
export let size: 'x-small' | 'small' = 'small' export let size: 'x-small' | 'small' = 'small'
let isChecked = value?.isChecked
const dispatch = createEventDispatcher()
const isOverdue = !!value?.dueDate && new Date().getTime() > value.dueDate const isOverdue = !!value?.dueDate && new Date().getTime() > value.dueDate
function check () {
if (isChecked === undefined || !value) return
dispatch('update', { ...value, isChecked })
}
</script> </script>
{#if value} {#if value}
<div class="flex-presenter flex-gap-1 h-full"> <div class="flex-presenter flex-gap-1 h-full">
{#if value.dueDate}
<CheckBox bind:checked={isChecked} on:value={check} />
{/if}
<div class="flex-center h-full" on:click> <div class="flex-center h-full" on:click>
<div class="flex-row-center background-button-bg-color pr-1 pl-1 border-radius-1 w-full"> <div class="flex-row-center background-button-bg-color pr-1 pl-1 border-radius-1 w-full">
{#if value.startDate} {#if value.startDate}

View File

@ -1,29 +0,0 @@
<script lang="ts">
import { Employee, formatName } from '@anticrm/contact'
import { getFirstName, getLastName } from '@anticrm/contact'
import { Button, showPopup } from '@anticrm/ui'
import type { IntlString } from '@anticrm/platform'
import EditMember from '../popups/EditMember.svelte'
import { getPopupAlignment } from '../../utils/PopupUtils'
export let value: Employee
export let size: 'large' | 'medium'
export let menuItems: { title: IntlString; handler: () => void }[][]
const openPopup = (e: Event) => {
const onClose = () => closePopup()
const closePopup = showPopup(EditMember, { member: value, menuItems, onClose }, getPopupAlignment(e))
}
$: firstName = getFirstName(value.name)
$: lastName = getLastName(value.name)
$: nameLabel = `${firstName?.[0] ?? ''}${lastName?.[0] ?? ''}`.toUpperCase()
$: formattedName = formatName(value.name)
</script>
{#if value}
<Button {size} kind="no-border" shape="circle" title={formattedName} on:click={openPopup}>
<div slot="content" class="text-md">{nameLabel}</div>
</Button>
{/if}

View File

@ -16,7 +16,6 @@
import { Resources } from '@anticrm/platform' import { Resources } from '@anticrm/platform'
import { TodoItem } from '@anticrm/task' import { TodoItem } from '@anticrm/task'
import { getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
import board from '@anticrm/board'
import BoardPresenter from './components/BoardPresenter.svelte' import BoardPresenter from './components/BoardPresenter.svelte'
import CardPresenter from './components/CardPresenter.svelte' import CardPresenter from './components/CardPresenter.svelte'
@ -29,7 +28,6 @@ import CardLabelsPopup from './components/popups/CardLabelsPopup.svelte'
import MoveCard from './components/popups/MoveCard.svelte' import MoveCard from './components/popups/MoveCard.svelte'
import CopyCard from './components/popups/CopyCard.svelte' import CopyCard from './components/popups/CopyCard.svelte'
import DateRangePicker from './components/popups/DateRangePicker.svelte' import DateRangePicker from './components/popups/DateRangePicker.svelte'
import CardDatePresenter from './components/presenters/DatePresenter.svelte'
import CardLabelPresenter from './components/presenters/LabelPresenter.svelte' import CardLabelPresenter from './components/presenters/LabelPresenter.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte' import TemplatesIcon from './components/TemplatesIcon.svelte'
import BoardHeader from './components/BoardHeader.svelte' import BoardHeader from './components/BoardHeader.svelte'
@ -46,15 +44,12 @@ async function ConvertToCard (object: TodoItem): Promise<void> {
const client = getClient() const client = getClient()
const todoItemCard = await getCardFromTodoItem(client, object) const todoItemCard = await getCardFromTodoItem(client, object)
if (todoItemCard === undefined) return if (todoItemCard === undefined) return
const date =
object.dueTo === null
? {}
: { date: { _class: board.class.CardDate, dueDate: object.dueTo, isChecked: object.done } }
await createCard(client, todoItemCard.space, todoItemCard.state, { await createCard(client, todoItemCard.space, todoItemCard.state, {
title: object.name, title: object.name,
assignee: object.assignee, assignee: object.assignee,
...date dueDate: object.dueTo
}) })
await client.remove(object) await client.remove(object)
} }
@ -65,7 +60,6 @@ export default async (): Promise<Resources> => ({
EditCard, EditCard,
KanbanCard, KanbanCard,
CardPresenter, CardPresenter,
CardDatePresenter,
CardLabelPresenter, CardLabelPresenter,
TemplatesIcon, TemplatesIcon,
KanbanView, KanbanView,

View File

@ -19,6 +19,7 @@ import type { AnyComponent } from '@anticrm/ui'
export default mergeIds(boardId, board, { export default mergeIds(boardId, board, {
string: { string: {
Completed: '' as IntlString,
Name: '' as IntlString, Name: '' as IntlString,
BoardName: '' as IntlString, BoardName: '' as IntlString,
MakePrivate: '' as IntlString, MakePrivate: '' as IntlString,
@ -49,17 +50,14 @@ export default mergeIds(boardId, board, {
IsArchived: '' as IntlString, IsArchived: '' as IntlString,
BoardCreateLabel: '' as IntlString, BoardCreateLabel: '' as IntlString,
Settings: '' as IntlString, Settings: '' as IntlString,
InList: '' as IntlString,
Suggested: '' as IntlString, Suggested: '' as IntlString,
AddToCard: '' as IntlString,
Labels: '' as IntlString, Labels: '' as IntlString,
CreateLabel: '' as IntlString, CreateLabel: '' as IntlString,
SearchLabels: '' as IntlString, SearchLabels: '' as IntlString,
SelectColor: '' as IntlString, SelectColor: '' as IntlString,
NoColor: '' as IntlString, NoColor: '' as IntlString,
NoColorInfo: '' as IntlString, NoColorInfo: '' as IntlString,
Checklist: '' as IntlString, Checklists: '' as IntlString,
AddChecklistItem: '' as IntlString,
ChecklistDropdownNone: '' as IntlString, ChecklistDropdownNone: '' as IntlString,
ShowDoneChecklistItems: '' as IntlString, ShowDoneChecklistItems: '' as IntlString,
HideDoneChecklistItems: '' as IntlString, HideDoneChecklistItems: '' as IntlString,
@ -101,8 +99,6 @@ export default mergeIds(boardId, board, {
List: '' as IntlString, List: '' as IntlString,
Position: '' as IntlString, Position: '' as IntlString,
Current: '' as IntlString, Current: '' as IntlString,
StartDate: '' as IntlString,
DueDate: '' as IntlString,
Save: '' as IntlString, Save: '' as IntlString,
Remove: '' as IntlString, Remove: '' as IntlString,
NullDate: '' as IntlString, NullDate: '' as IntlString,

View File

@ -31,6 +31,8 @@ export async function createCard (
title: '', title: '',
state, state,
doneState: null, doneState: null,
startDate: null,
dueDate: null,
number: (incResult as any).object.sequence, number: (incResult as any).object.sequence,
rank: calcRank(lastOne, undefined), rank: calcRank(lastOne, undefined),
assignee: null, assignee: null,
@ -95,7 +97,7 @@ export function hasCover (card: Card): boolean {
} }
export function hasDate (card: Card): boolean { export function hasDate (card: Card): boolean {
return card.date !== undefined && (card.date.dueDate !== undefined || card.date.startDate !== undefined) return card.dueDate !== undefined || card.startDate !== undefined
} }
export function addCurrentUser (card: Card, client: Client): Promise<TxResult> | undefined { export function addCurrentUser (card: Card, client: Client): Promise<TxResult> | undefined {

View File

@ -15,11 +15,11 @@
// //
import { Employee } from '@anticrm/contact' import { Employee } from '@anticrm/contact'
import type { AttachedDoc, Class, Doc, Markup, Ref, Timestamp, Obj } from '@anticrm/core' import type { AttachedDoc, Class, Doc, Markup, Ref } from '@anticrm/core'
import type { Asset, IntlString, Plugin } from '@anticrm/platform' import type { Asset, IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform'
import type { Preference } from '@anticrm/preference' import type { Preference } from '@anticrm/preference'
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task' import type { DoneState, KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task'
import type { AnyComponent } from '@anticrm/ui' import type { AnyComponent } from '@anticrm/ui'
import { Action, ActionCategory } from '@anticrm/view' import { Action, ActionCategory } from '@anticrm/view'
@ -49,15 +49,6 @@ export interface CardLabel extends AttachedDoc {
isHidden?: boolean isHidden?: boolean
} }
/**
* @public
*/
export interface CardDate extends Obj {
dueDate?: Timestamp
isChecked?: boolean
startDate?: Timestamp
}
/** /**
* @public * @public
*/ */
@ -72,8 +63,6 @@ export interface CardCover {
*/ */
export interface Card extends Task { export interface Card extends Task {
title: string title: string
date?: CardDate
description: Markup description: Markup
isArchived?: boolean isArchived?: boolean
@ -120,7 +109,6 @@ const boards = plugin(boardId, {
class: { class: {
Board: '' as Ref<Class<Board>>, Board: '' as Ref<Class<Board>>,
Card: '' as Ref<Class<Card>>, Card: '' as Ref<Class<Card>>,
CardDate: '' as Ref<Class<CardDate>>,
CardLabel: '' as Ref<Class<CardLabel>>, CardLabel: '' as Ref<Class<CardLabel>>,
MenuPage: '' as Ref<Class<MenuPage>>, MenuPage: '' as Ref<Class<MenuPage>>,
CommonBoardPreference: '' as Ref<Class<CommonBoardPreference>> CommonBoardPreference: '' as Ref<Class<CommonBoardPreference>>
@ -128,6 +116,9 @@ const boards = plugin(boardId, {
category: { category: {
Card: '' as Ref<ActionCategory> Card: '' as Ref<ActionCategory>
}, },
state: {
Completed: '' as Ref<DoneState>
},
action: { action: {
Cover: '' as Ref<Action>, Cover: '' as Ref<Action>,
Dates: '' as Ref<Action>, Dates: '' as Ref<Action>,

View File

@ -64,7 +64,9 @@
number: (incResult as any).object.sequence, number: (incResult as any).object.sequence,
title: title, title: title,
rank: calcRank(lastOne, undefined), rank: calcRank(lastOne, undefined),
assignee: null assignee: null,
startDate: null,
dueDate: null
} }
const customerInstance = await client.findOne(contact.class.Contact, { _id: customer! }) const customerInstance = await client.findOne(contact.class.Contact, { _id: customer! })

View File

@ -56,7 +56,9 @@
_id: generateId(), _id: generateId(),
collection: 'applications', collection: 'applications',
modifiedOn: Date.now(), modifiedOn: Date.now(),
modifiedBy: '' as Ref<Account> modifiedBy: '' as Ref<Account>,
startDate: null,
dueDate: null
} }
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -109,7 +111,9 @@
doneState: null, doneState: null,
number: (incResult as any).object.sequence, number: (incResult as any).object.sequence,
assignee: doc.assignee, assignee: doc.assignee,
rank: calcRank(lastOne, undefined) rank: calcRank(lastOne, undefined),
startDate: null,
dueDate: null
} }
) )
@ -128,7 +132,9 @@
_id: generateId(), _id: generateId(),
collection: 'applications', collection: 'applications',
modifiedOn: Date.now(), modifiedOn: Date.now(),
modifiedBy: '' as Ref<Account> modifiedBy: '' as Ref<Account>,
startDate: null,
dueDate: null
} }
} }
} }

View File

@ -1,5 +1,7 @@
{ {
"string": { "string": {
"StartDate": "Start date",
"DueDate": "Due date",
"TaskState": "State", "TaskState": "State",
"TaskStateTitle": "Title", "TaskStateTitle": "Title",
"TaskStateDone": "Done", "TaskStateDone": "Done",

View File

@ -1,5 +1,7 @@
{ {
"string": { "string": {
"StartDate": "Начало",
"DueDate": "Срок",
"TaskState": "Статус", "TaskState": "Статус",
"TaskStateTitle": "Заголовок", "TaskStateTitle": "Заголовок",
"TaskStateDone": "Завершен", "TaskStateDone": "Завершен",

View File

@ -81,7 +81,8 @@ export interface Task extends AttachedDoc, DocWithRank {
doneState: Ref<DoneState> | null doneState: Ref<DoneState> | null
number: number number: number
assignee: Ref<Employee> | null assignee: Ref<Employee> | null
dueDate: Timestamp | null
startDate: Timestamp | null
todoItems?: number todoItems?: number
} }
@ -196,6 +197,34 @@ const task = plugin(taskId, {
interface: { interface: {
DocWithRank: '' as Ref<Interface<DocWithRank>> DocWithRank: '' as Ref<Interface<DocWithRank>>
}, },
string: {
StartDate: '' as IntlString,
DueDate: '' as IntlString,
TaskState: '' as IntlString,
TaskStateTitle: '' as IntlString,
TaskStateDone: '' as IntlString,
TaskNumber: '' as IntlString,
Todo: '' as IntlString,
TaskDone: '' as IntlString,
TaskDueTo: '' as IntlString,
TaskParent: '' as IntlString,
IssueName: '' as IntlString,
TaskComments: '' as IntlString,
TaskLabels: '' as IntlString,
StateTemplateTitle: '' as IntlString,
StateTemplateColor: '' as IntlString,
KanbanTemplateTitle: '' as IntlString,
Rank: '' as IntlString,
EditStates: '' as IntlString,
MarkAsDone: '' as IntlString,
MarkAsUndone: '' as IntlString,
Kanban: '' as IntlString,
ApplicationLabelTask: '' as IntlString,
Projects: '' as IntlString,
SearchTask: '' as IntlString,
ManageProjectStatues: '' as IntlString,
TodoItems: '' as IntlString
},
class: { class: {
Issue: '' as Ref<Class<Issue>>, Issue: '' as Ref<Class<Issue>>,
Project: '' as Ref<Class<Project>>, Project: '' as Ref<Class<Project>>,
@ -239,9 +268,6 @@ const task = plugin(taskId, {
KanbanTemplateEditor: '' as AnyComponent, KanbanTemplateEditor: '' as AnyComponent,
KanbanTemplateSelector: '' as AnyComponent, KanbanTemplateSelector: '' as AnyComponent,
TodoItemsPopup: '' as AnyComponent TodoItemsPopup: '' as AnyComponent
},
string: {
TodoItems: '' as IntlString
} }
}) })