mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
parent
5f5102a939
commit
e4b7575147
@ -156,7 +156,9 @@ async function genApplicant (
|
||||
assignee: faker.random.arrayElement(emoloyeeIds),
|
||||
state: faker.random.arrayElement(states),
|
||||
doneState: null,
|
||||
rank: rank as string
|
||||
rank: rank as string,
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
|
||||
// Update or create candidate
|
||||
|
@ -251,7 +251,9 @@ async function createApplicant (
|
||||
assignee: null,
|
||||
state,
|
||||
doneState: null,
|
||||
rank: calcRank(lastOne, undefined)
|
||||
rank: calcRank(lastOne, undefined),
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
|
||||
// Update or create candidate
|
||||
|
@ -14,9 +14,9 @@
|
||||
//
|
||||
|
||||
// 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 { DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp, Type } from '@anticrm/core'
|
||||
import { DOMAIN_MODEL, IndexKind, Markup, Ref } from '@anticrm/core'
|
||||
import {
|
||||
ArrOf,
|
||||
Builder,
|
||||
@ -33,7 +33,7 @@ import {
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
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 view, { actionTemplates, createAction } from '@anticrm/model-view'
|
||||
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 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)
|
||||
@UX(board.string.Board, board.icon.Board)
|
||||
export class TBoard extends TSpaceWithStates implements Board {
|
||||
@ -56,14 +49,6 @@ export class TBoard extends TSpaceWithStates implements Board {
|
||||
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)
|
||||
@UX(board.string.Labels)
|
||||
export class TCardLabel extends TAttachedDoc implements CardLabel {
|
||||
@ -90,9 +75,6 @@ export class TCard extends TTask implements Card {
|
||||
@Prop(TypeBoolean(), board.string.IsArchived)
|
||||
isArchived?: boolean
|
||||
|
||||
@Prop(TypeCardDate(), board.string.Dates)
|
||||
date?: CardDate
|
||||
|
||||
@Prop(TypeMarkup(), board.string.Description)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: Markup
|
||||
@ -125,7 +107,7 @@ export class TMenuPage extends TDoc implements MenuPage {
|
||||
}
|
||||
|
||||
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, {
|
||||
component: board.component.Archive,
|
||||
@ -228,10 +210,6 @@ export function createModel (builder: Builder): void {
|
||||
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, {
|
||||
presenter: board.component.BoardPresenter
|
||||
})
|
||||
@ -269,6 +247,16 @@ export function createModel (builder: Builder): void {
|
||||
board.viewlet.Table
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
task.class.WonState,
|
||||
core.space.Model,
|
||||
{
|
||||
title: board.string.Completed,
|
||||
rank: '0'
|
||||
},
|
||||
board.state.Completed
|
||||
)
|
||||
|
||||
// card actions
|
||||
createAction(
|
||||
builder,
|
||||
|
@ -29,7 +29,6 @@ export default mergeIds(boardId, board, {
|
||||
KanbanCard: '' as AnyComponent,
|
||||
CardPresenter: '' as AnyComponent,
|
||||
CardLabelPresenter: '' as AnyComponent,
|
||||
CardDatePresenter: '' as AnyComponent,
|
||||
BoardPresenter: '' as AnyComponent,
|
||||
TemplatesIcon: '' as AnyComponent,
|
||||
Cards: '' as AnyComponent,
|
||||
|
@ -113,6 +113,12 @@ export class TTask extends TAttachedDoc implements Task {
|
||||
// @Prop(TypeRef(contact.class.Employee), task.string.TaskAssignee)
|
||||
assignee!: Ref<Employee> | null
|
||||
|
||||
@Prop(TypeDate(), task.string.DueDate)
|
||||
dueDate!: Timestamp | null
|
||||
|
||||
@Prop(TypeDate(), task.string.StartDate)
|
||||
startDate!: Timestamp | null
|
||||
|
||||
declare rank: string
|
||||
|
||||
@Prop(Collection(task.class.TodoItem), task.string.Todos)
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import type { Ref, Space } from '@anticrm/core'
|
||||
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 { KanbanTemplate, taskId } from '@anticrm/task'
|
||||
import task from '@anticrm/task-resources/src/plugin'
|
||||
@ -58,31 +58,6 @@ export default mergeIds(taskId, task, {
|
||||
StatusTableView: '' 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: {
|
||||
TasksPublic: '' as Ref<Space>
|
||||
},
|
||||
|
@ -451,6 +451,7 @@ input.search {
|
||||
.w-9 { width: 2.25rem; }
|
||||
.w-14 { width: 3.5rem; }
|
||||
.w-16 { width: 4rem; }
|
||||
.w-22 { width: 5.5rem; }
|
||||
.w-24 { width: 6rem; }
|
||||
.w-60 { width: 15rem; }
|
||||
.w-85 { width: 21.25rem; }
|
||||
|
@ -7,7 +7,7 @@
|
||||
"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}}",
|
||||
"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}}",
|
||||
"ShowMore": "Show more",
|
||||
"ShowLess": "Show less",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"string": {
|
||||
"Completed": "Completed",
|
||||
"Name": "Name",
|
||||
"CreateBoard": "Create board",
|
||||
"OpenCard": "Open Card",
|
||||
@ -28,17 +29,14 @@
|
||||
"IsArchived": "Archived",
|
||||
"BoardCreateLabel": "Board",
|
||||
"Settings": "Settings",
|
||||
"InList": "in list",
|
||||
"Suggested": "Suggested",
|
||||
"AddToCard": "Add to card",
|
||||
"Labels": "Labels",
|
||||
"CreateLabel": "Create a new label",
|
||||
"SearchLabels": "Search labels...",
|
||||
"SelectColor": "Select a color",
|
||||
"NoColor": "No color.",
|
||||
"NoColorInfo": "This won't show up on the front of cards.",
|
||||
"Checklist": "Checklist",
|
||||
"AddChecklistItem": "Add an item",
|
||||
"Checklists": "Checklists",
|
||||
"ChecklistDropdownNone": "(none)",
|
||||
"ShowDoneChecklistItems": "Show checked items ({done})",
|
||||
"HideDoneChecklistItems": "Hide checked items",
|
||||
@ -80,8 +78,6 @@
|
||||
"List": "List",
|
||||
"Position": "Position",
|
||||
"Current": "{label} (current)",
|
||||
"StartDate": "Start date",
|
||||
"DueDate": "Due date",
|
||||
"Save": "Save",
|
||||
"Remove": "Remove",
|
||||
"NullDate": "M/D/YYYY",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"string": {
|
||||
"Completed": "Завершено",
|
||||
"Name": "Название",
|
||||
"CreateBoard": "Создать",
|
||||
"OpenCard": "Открыть",
|
||||
@ -28,17 +29,14 @@
|
||||
"IsArchived": "Архивировано",
|
||||
"BoardCreateLabel": "Board",
|
||||
"Settings": "Настройки",
|
||||
"InList": "в списке",
|
||||
"Suggested": "Предложенное",
|
||||
"AddToCard": "Добавить",
|
||||
"Labels": "Метки",
|
||||
"CreateLabel": "Создать",
|
||||
"SearchLabels": "Поиск...",
|
||||
"SelectColor": "Выберите цвет",
|
||||
"NoColor": "Без цвета.",
|
||||
"NoColorInfo": "Не будет показываться на доске.",
|
||||
"Checklist": "Списки",
|
||||
"AddChecklistItem": "Добавить",
|
||||
"Checklists": "Списки",
|
||||
"ChecklistDropdownNone": "(не выбрано)",
|
||||
"ShowDoneChecklistItems": "Показать отмеченные ({done})",
|
||||
"HideDoneChecklistItems": "Скрыть отмеченные",
|
||||
@ -80,8 +78,6 @@
|
||||
"List": "Список",
|
||||
"Position": "Позиция",
|
||||
"Current": "{label} (текущий)",
|
||||
"StartDate": "Начало",
|
||||
"DueDate": "Срок",
|
||||
"Save": "Сохранить",
|
||||
"Remove": "Удалить",
|
||||
"NullDate": "М/Д/ГГГГ",
|
||||
|
@ -66,7 +66,9 @@
|
||||
description: '',
|
||||
members: [],
|
||||
labels: [],
|
||||
location: ''
|
||||
location: '',
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
|
||||
await client.addCollection(board.class.Card, _space, space, board.class.Board, 'cards', value, cardId)
|
||||
|
@ -14,23 +14,24 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Attachments } from '@anticrm/attachment-resources'
|
||||
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 { createQuery, getClient } from '@anticrm/presentation'
|
||||
import type { State, TodoItem } from '@anticrm/task'
|
||||
import task from '@anticrm/task'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { Button, EditBox, Icon, Label } from '@anticrm/ui'
|
||||
import { invokeAction, UpDownNavigator } from '@anticrm/view-resources'
|
||||
import { Button, CircleButton, EditBox, IconAdd, IconMoreH, Label, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu, invokeAction, UpDownNavigator } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import board from '../plugin'
|
||||
import { getCardActions } from '../utils/CardActionUtils'
|
||||
import { updateCard } from '../utils/CardUtils'
|
||||
import { getPopupAlignment } from '../utils/PopupUtils'
|
||||
import CardActions from './editor/CardActions.svelte'
|
||||
import CardAttachments from './editor/CardAttachments.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 _class: Ref<Class<Card>>
|
||||
@ -38,10 +39,12 @@
|
||||
const client = getClient()
|
||||
const cardQuery = createQuery()
|
||||
const stateQuery = createQuery()
|
||||
const spaceQuery = createQuery()
|
||||
const checklistsQuery = createQuery()
|
||||
|
||||
let object: Card | undefined
|
||||
let state: State | undefined
|
||||
let space: Space | undefined
|
||||
let handleMove: (e: Event) => void
|
||||
let checklists: TodoItem[] = []
|
||||
|
||||
@ -51,6 +54,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addChecklist (e: Event) {
|
||||
showPopup(AddChecklist, { value: object }, getPopupAlignment(e))
|
||||
}
|
||||
|
||||
$: cardQuery.query(_class, { _id }, (result) => {
|
||||
object = result[0]
|
||||
})
|
||||
@ -60,6 +67,11 @@
|
||||
state = result[0]
|
||||
})
|
||||
|
||||
$: object?.space &&
|
||||
spaceQuery.query(core.class.Space, { _id: object.space }, (result) => {
|
||||
space = result[0]
|
||||
})
|
||||
|
||||
$: object &&
|
||||
checklistsQuery.query(task.class.TodoItem, { space: object.space, attachedTo: object._id }, (result) => {
|
||||
checklists = result
|
||||
@ -86,8 +98,9 @@
|
||||
icon={board.icon.Card}
|
||||
title={object?.title}
|
||||
{object}
|
||||
isHeader={false}
|
||||
isHeader
|
||||
isAside={true}
|
||||
isSub={false}
|
||||
isFullSize
|
||||
on:fullsize
|
||||
on:close={() => dispatch('close')}
|
||||
@ -95,40 +108,33 @@
|
||||
<svelte:fragment slot="navigator">
|
||||
<UpDownNavigator element={object} />
|
||||
</svelte:fragment>
|
||||
|
||||
<!-- TODO cover -->
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9">
|
||||
<Icon icon={board.icon.Card} size="large" />
|
||||
<svelte:fragment slot="header">
|
||||
<div class="flex fs-title flex-gap-1">
|
||||
<span class="over-underline" on:click={handleMove}>{space?.name}</span>><span
|
||||
class="over-underline"
|
||||
on:click={handleMove}>{state?.title}</span
|
||||
>
|
||||
</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)} />
|
||||
</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-grow mr-4">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9" />
|
||||
<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">
|
||||
<div class="background-bg-accent border-bg-accent border-radius-3 p-2 mt-2 w-full">
|
||||
<StyledTextBox
|
||||
alwaysEdit={true}
|
||||
showButtons={false}
|
||||
@ -138,13 +144,22 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CardAttachments value={object} />
|
||||
<div class="mt-6">
|
||||
<Attachments objectId={_id} {_class} space={object.space} attachments={object.attachments ?? 0} />
|
||||
</div>
|
||||
<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>
|
||||
<span slot="actions-label"><Label label={board.string.Card} /></span>
|
||||
<span slot="actions" />
|
||||
<svelte:fragment slot="custom-attributes" let:direction>
|
||||
{#if direction === 'column'}
|
||||
<CardActions bind:value={object} />
|
||||
@ -154,13 +169,3 @@
|
||||
</svelte:fragment>
|
||||
</Panel>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.state-name {
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
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 contact, { Employee } from '@anticrm/contact'
|
||||
import type { Ref, WithLookup } from '@anticrm/core'
|
||||
@ -62,9 +62,6 @@
|
||||
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)}` : ''
|
||||
</script>
|
||||
|
||||
@ -178,9 +175,9 @@
|
||||
<div class="float-left">
|
||||
<NotificationPresenter {object} />
|
||||
</div>
|
||||
{#if object.date && hasDate(object)}
|
||||
{#if hasDate(object)}
|
||||
<div class="float-left">
|
||||
<DatePresenter value={object.date} size="x-small" on:update={updateDate} />
|
||||
<DatePresenter value={object} size="x-small" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if object.description}
|
||||
|
@ -24,7 +24,8 @@
|
||||
'title',
|
||||
'$lookup.state',
|
||||
{ key: '', presenter: board.component.CardLabels, label: board.string.Labels },
|
||||
'date',
|
||||
'startDate',
|
||||
'dueDate',
|
||||
{ key: 'members', presenter: board.component.UserBoxList, label: board.string.Members, sortingKey: '' },
|
||||
'modifiedOn'
|
||||
]}
|
||||
|
@ -8,4 +8,4 @@
|
||||
export let value: Ref<Employee>[]
|
||||
</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 />
|
||||
|
@ -53,7 +53,9 @@
|
||||
description: '',
|
||||
members: [],
|
||||
labels: [],
|
||||
location: ''
|
||||
location: '',
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
|
||||
return client.addCollection(board.class.Card, space, space, board.class.Board, 'cards', value, newCardId)
|
||||
|
@ -15,78 +15,101 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
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 { Button, IconAttachment, Label, showPopup } from '@anticrm/ui'
|
||||
import type { Action } from '@anticrm/view'
|
||||
import { getActions, invokeAction } from '@anticrm/view-resources'
|
||||
import AddChecklist from '../popups/AddChecklist.svelte'
|
||||
import AttachmentPicker from '../popups/AttachmentPicker.svelte'
|
||||
import ui, { Button, CheckBox, DateRangePresenter, Label, IconAdd } from '@anticrm/ui'
|
||||
import { invokeAction } from '@anticrm/view-resources'
|
||||
|
||||
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
|
||||
const client = getClient()
|
||||
|
||||
let topActions: Action[] = []
|
||||
let toolsActions: Action[] = []
|
||||
async function fetch () {
|
||||
const result = await getActions(client, value, value._class)
|
||||
topActions = result.filter((action) => action.context.group === 'top')
|
||||
toolsActions = result.filter((action) => action.context.group !== 'top')
|
||||
let coverHandler: (e: Event) => void
|
||||
|
||||
function updateMembers (e: CustomEvent<Ref<Employee>[]>) {
|
||||
updateCardMembers(value, client, e.detail)
|
||||
}
|
||||
fetch()
|
||||
$: value.members && fetch()
|
||||
$: value.isArchived && fetch()
|
||||
$: !value.isArchived && fetch()
|
||||
function updateState (e: CustomEvent<boolean>) {
|
||||
if (e.detail) {
|
||||
client.update(value, { doneState: board.state.Completed })
|
||||
} 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>
|
||||
|
||||
{#if value}
|
||||
<div class="flex-col flex-gap-3">
|
||||
<div class="flex-col flex-gap-1">
|
||||
<Label label={plugin.string.AddToCard} />
|
||||
{#each topActions as action}
|
||||
<Button
|
||||
icon={action.icon}
|
||||
label={action.label}
|
||||
kind="no-border"
|
||||
justify="left"
|
||||
on:click={(e) => {
|
||||
invokeAction(value, e, action.action, action.actionProps)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<Button
|
||||
icon={plugin.icon.Card}
|
||||
label={plugin.string.Checklist}
|
||||
kind="no-border"
|
||||
justify="left"
|
||||
on:click={(e) => {
|
||||
showPopup(AddChecklist, { value }, getPopupAlignment(e))
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconAttachment}
|
||||
label={plugin.string.Attachments}
|
||||
kind="no-border"
|
||||
justify="left"
|
||||
on:click={(e) => {
|
||||
showPopup(AttachmentPicker, { value }, getPopupAlignment(e))
|
||||
<div class="flex-col flex-gap-3 mt-4">
|
||||
<div class="flex-row-stretch flex-gap-1 items-center">
|
||||
<div class="label w-24">
|
||||
<Label label={plugin.string.Completed} />
|
||||
</div>
|
||||
<CheckBox checked={value.doneState === board.state.Completed} on:value={updateState} />
|
||||
</div>
|
||||
<div class="flex-row-stretch flex-gap-1 items-center">
|
||||
<div class="label w-24">
|
||||
<Label label={plugin.string.Members} />
|
||||
</div>
|
||||
<UserBoxList value={value.members ?? []} on:update={updateMembers} />
|
||||
</div>
|
||||
<div class="flex-row-stretch flex-gap-1 items-center">
|
||||
<div class="label w-24">
|
||||
<Label label={plugin.string.Labels} />
|
||||
</div>
|
||||
<CardLabels {value} />
|
||||
</div>
|
||||
<div class="flex-row-stretch flex-gap-1 items-center">
|
||||
<div class="label w-24">
|
||||
<Label label={ui.string.StartDate} />
|
||||
</div>
|
||||
<DateRangePresenter
|
||||
value={value.startDate}
|
||||
editable={true}
|
||||
withTime={false}
|
||||
on:change={(e) => {
|
||||
console.log(e)
|
||||
client.update(value, { startDate: e.detail })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-col flex-gap-1">
|
||||
<Label label={plugin.string.Actions} />
|
||||
{#each toolsActions as action}
|
||||
<Button
|
||||
icon={action.icon}
|
||||
label={action.label}
|
||||
kind="no-border"
|
||||
justify="left"
|
||||
on:click={(e) => {
|
||||
invokeAction(value, e, action.action, action.actionProps)
|
||||
}}
|
||||
<div class="flex-row-stretch flex-gap-1 items-center">
|
||||
<div class="label w-24">
|
||||
<Label label={ui.string.DueDate} />
|
||||
</div>
|
||||
<DateRangePresenter
|
||||
value={value.dueDate}
|
||||
editable={true}
|
||||
withTime={false}
|
||||
on:change={(e) => client.update(value, { dueDate: e.detail })}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex-row-stretch flex-gap-1 items-center">
|
||||
<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>
|
||||
{/if}
|
||||
|
@ -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}
|
@ -21,7 +21,8 @@
|
||||
Button,
|
||||
CheckBox,
|
||||
TextAreaEditor,
|
||||
Icon,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
IconMoreH,
|
||||
Progress,
|
||||
showPopup,
|
||||
@ -131,9 +132,6 @@
|
||||
{#if value !== undefined}
|
||||
<div class="flex-col w-full">
|
||||
<div class="flex-row-stretch mt-4 mb-2">
|
||||
<div class="w-9">
|
||||
<Icon icon={board.icon.Card} size="large" />
|
||||
</div>
|
||||
{#if isEditingName}
|
||||
<div class="flex-grow">
|
||||
<TextAreaEditor
|
||||
@ -154,19 +152,18 @@
|
||||
{value.name}
|
||||
</div>
|
||||
{#if done > 0}
|
||||
<div class="mr-1">
|
||||
<Button
|
||||
label={hideDoneItems ? board.string.ShowDoneChecklistItems : board.string.HideDoneChecklistItems}
|
||||
labelParams={{ done }}
|
||||
kind="no-border"
|
||||
kind="transparent"
|
||||
size="small"
|
||||
on:click={() => {
|
||||
hideDoneItems = !hideDoneItems
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/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}
|
||||
</div>
|
||||
<div class="flex-row-stretch mb-2 mt-1">
|
||||
@ -254,8 +251,6 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<Button label={board.string.AddChecklistItem} kind="no-border" size="small" on:click={startAddingItem} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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}
|
@ -68,7 +68,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if labels && labels.length > 0}
|
||||
{#if labels}
|
||||
<div
|
||||
class="flex-row-center flex-wrap flex-gap-1 mb-1"
|
||||
class:labels-inline-container={isInline}
|
||||
|
@ -106,7 +106,7 @@
|
||||
<div class="antiPopup w-85">
|
||||
<div class="relative flex-row-center w-full ">
|
||||
<div class="flex-center flex-grow fs-title mt-1 mb-1">
|
||||
<Label label={board.string.Checklist} />
|
||||
<Label label={board.string.Checklists} />
|
||||
</div>
|
||||
|
||||
<div class="absolute mr-1 mt-1 mb-1" style:top="0" style:right="0">
|
||||
|
@ -52,7 +52,9 @@
|
||||
description: '',
|
||||
members: [],
|
||||
location: '',
|
||||
labels: labels ?? []
|
||||
labels: labels ?? [],
|
||||
startDate: value.startDate,
|
||||
dueDate: value.dueDate
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
|
@ -1,35 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Label, Button, DateRangePresenter, CheckBox, Component } from '@anticrm/ui'
|
||||
import { Card, CardDate } from '@anticrm/board'
|
||||
import { Card } from '@anticrm/board'
|
||||
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 { getClient } from '@anticrm/presentation'
|
||||
|
||||
export let value: Card
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let startDate = value.date?.startDate
|
||||
let savedStartDate = value.date?.startDate ?? Date.now()
|
||||
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 }
|
||||
}
|
||||
let startDate = value.startDate
|
||||
let dueDate = value.dueDate
|
||||
|
||||
function update () {
|
||||
const date: CardDate = getEmptyDate()
|
||||
const date: DocumentUpdate<Card> = {}
|
||||
if (startDate !== undefined) date.startDate = startDate
|
||||
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>
|
||||
|
||||
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle antiPopup-withCategory w-85">
|
||||
@ -39,35 +41,19 @@
|
||||
</div>
|
||||
<div class="ap-space bottom-divider" />
|
||||
<div class="ap-category">
|
||||
<div class="categoryItem flex-center whitespace-nowrap">
|
||||
<Label label={board.string.StartDate} />
|
||||
</div>
|
||||
<div class="categoryItem p-2 flex-center">
|
||||
<CheckBox
|
||||
bind:checked={startDateEnabled}
|
||||
on:value={() => {
|
||||
startDate = startDateEnabled ? savedStartDate : undefined
|
||||
}}
|
||||
/>
|
||||
<div class="categoryItem flex-center whitespace-nowrap w-22">
|
||||
<Label label={task.string.StartDate} />
|
||||
</div>
|
||||
<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 class="ap-category">
|
||||
<div class="categoryItem flex-center whitespace-nowrap">
|
||||
<Label label={board.string.DueDate} />
|
||||
</div>
|
||||
<div class="categoryItem p-2 flex-center">
|
||||
<CheckBox
|
||||
bind:checked={dueDateEnabled}
|
||||
on:value={() => {
|
||||
dueDate = dueDateEnabled ? savedDueDate : undefined
|
||||
}}
|
||||
/>
|
||||
<div class="categoryItem flex-center whitespace-nowrap w-22">
|
||||
<Label label={task.string.DueDate} />
|
||||
</div>
|
||||
<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 class="ap-footer">
|
||||
@ -81,9 +67,10 @@
|
||||
<Button
|
||||
label={board.string.Remove}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
client.update(value, { date: getEmptyDate() })
|
||||
dispatch('close')
|
||||
on:click={async () => {
|
||||
await client.update(value, { startDate: null, dueDate: null })
|
||||
startDate = null
|
||||
dueDate = null
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -1,26 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { CardDate } from '@anticrm/board'
|
||||
import { CheckBox, DatePresenter } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Card } from '@anticrm/board'
|
||||
import { DatePresenter } from '@anticrm/ui'
|
||||
|
||||
export let value: CardDate
|
||||
export let value: Card
|
||||
export let size: 'x-small' | 'small' = 'small'
|
||||
|
||||
let isChecked = value?.isChecked
|
||||
const dispatch = createEventDispatcher()
|
||||
const isOverdue = !!value?.dueDate && new Date().getTime() > value.dueDate
|
||||
|
||||
function check () {
|
||||
if (isChecked === undefined || !value) return
|
||||
dispatch('update', { ...value, isChecked })
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<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-row-center background-button-bg-color pr-1 pl-1 border-radius-1 w-full">
|
||||
{#if value.startDate}
|
||||
|
@ -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}
|
@ -16,7 +16,6 @@
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import { TodoItem } from '@anticrm/task'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import board from '@anticrm/board'
|
||||
|
||||
import BoardPresenter from './components/BoardPresenter.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 CopyCard from './components/popups/CopyCard.svelte'
|
||||
import DateRangePicker from './components/popups/DateRangePicker.svelte'
|
||||
import CardDatePresenter from './components/presenters/DatePresenter.svelte'
|
||||
import CardLabelPresenter from './components/presenters/LabelPresenter.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import BoardHeader from './components/BoardHeader.svelte'
|
||||
@ -46,15 +44,12 @@ async function ConvertToCard (object: TodoItem): Promise<void> {
|
||||
const client = getClient()
|
||||
const todoItemCard = await getCardFromTodoItem(client, object)
|
||||
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, {
|
||||
title: object.name,
|
||||
assignee: object.assignee,
|
||||
...date
|
||||
dueDate: object.dueTo
|
||||
})
|
||||
|
||||
await client.remove(object)
|
||||
}
|
||||
|
||||
@ -65,7 +60,6 @@ export default async (): Promise<Resources> => ({
|
||||
EditCard,
|
||||
KanbanCard,
|
||||
CardPresenter,
|
||||
CardDatePresenter,
|
||||
CardLabelPresenter,
|
||||
TemplatesIcon,
|
||||
KanbanView,
|
||||
|
@ -19,6 +19,7 @@ import type { AnyComponent } from '@anticrm/ui'
|
||||
|
||||
export default mergeIds(boardId, board, {
|
||||
string: {
|
||||
Completed: '' as IntlString,
|
||||
Name: '' as IntlString,
|
||||
BoardName: '' as IntlString,
|
||||
MakePrivate: '' as IntlString,
|
||||
@ -49,17 +50,14 @@ export default mergeIds(boardId, board, {
|
||||
IsArchived: '' as IntlString,
|
||||
BoardCreateLabel: '' as IntlString,
|
||||
Settings: '' as IntlString,
|
||||
InList: '' as IntlString,
|
||||
Suggested: '' as IntlString,
|
||||
AddToCard: '' as IntlString,
|
||||
Labels: '' as IntlString,
|
||||
CreateLabel: '' as IntlString,
|
||||
SearchLabels: '' as IntlString,
|
||||
SelectColor: '' as IntlString,
|
||||
NoColor: '' as IntlString,
|
||||
NoColorInfo: '' as IntlString,
|
||||
Checklist: '' as IntlString,
|
||||
AddChecklistItem: '' as IntlString,
|
||||
Checklists: '' as IntlString,
|
||||
ChecklistDropdownNone: '' as IntlString,
|
||||
ShowDoneChecklistItems: '' as IntlString,
|
||||
HideDoneChecklistItems: '' as IntlString,
|
||||
@ -101,8 +99,6 @@ export default mergeIds(boardId, board, {
|
||||
List: '' as IntlString,
|
||||
Position: '' as IntlString,
|
||||
Current: '' as IntlString,
|
||||
StartDate: '' as IntlString,
|
||||
DueDate: '' as IntlString,
|
||||
Save: '' as IntlString,
|
||||
Remove: '' as IntlString,
|
||||
NullDate: '' as IntlString,
|
||||
|
@ -31,6 +31,8 @@ export async function createCard (
|
||||
title: '',
|
||||
state,
|
||||
doneState: null,
|
||||
startDate: null,
|
||||
dueDate: null,
|
||||
number: (incResult as any).object.sequence,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
assignee: null,
|
||||
@ -95,7 +97,7 @@ export function hasCover (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 {
|
||||
|
@ -15,11 +15,11 @@
|
||||
//
|
||||
|
||||
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 { plugin } from '@anticrm/platform'
|
||||
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 { Action, ActionCategory } from '@anticrm/view'
|
||||
|
||||
@ -49,15 +49,6 @@ export interface CardLabel extends AttachedDoc {
|
||||
isHidden?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CardDate extends Obj {
|
||||
dueDate?: Timestamp
|
||||
isChecked?: boolean
|
||||
startDate?: Timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -72,8 +63,6 @@ export interface CardCover {
|
||||
*/
|
||||
export interface Card extends Task {
|
||||
title: string
|
||||
|
||||
date?: CardDate
|
||||
description: Markup
|
||||
|
||||
isArchived?: boolean
|
||||
@ -120,7 +109,6 @@ const boards = plugin(boardId, {
|
||||
class: {
|
||||
Board: '' as Ref<Class<Board>>,
|
||||
Card: '' as Ref<Class<Card>>,
|
||||
CardDate: '' as Ref<Class<CardDate>>,
|
||||
CardLabel: '' as Ref<Class<CardLabel>>,
|
||||
MenuPage: '' as Ref<Class<MenuPage>>,
|
||||
CommonBoardPreference: '' as Ref<Class<CommonBoardPreference>>
|
||||
@ -128,6 +116,9 @@ const boards = plugin(boardId, {
|
||||
category: {
|
||||
Card: '' as Ref<ActionCategory>
|
||||
},
|
||||
state: {
|
||||
Completed: '' as Ref<DoneState>
|
||||
},
|
||||
action: {
|
||||
Cover: '' as Ref<Action>,
|
||||
Dates: '' as Ref<Action>,
|
||||
|
@ -64,7 +64,9 @@
|
||||
number: (incResult as any).object.sequence,
|
||||
title: title,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
assignee: null
|
||||
assignee: null,
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
|
||||
const customerInstance = await client.findOne(contact.class.Contact, { _id: customer! })
|
||||
|
@ -56,7 +56,9 @@
|
||||
_id: generateId(),
|
||||
collection: 'applications',
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: '' as Ref<Account>
|
||||
modifiedBy: '' as Ref<Account>,
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -109,7 +111,9 @@
|
||||
doneState: null,
|
||||
number: (incResult as any).object.sequence,
|
||||
assignee: doc.assignee,
|
||||
rank: calcRank(lastOne, undefined)
|
||||
rank: calcRank(lastOne, undefined),
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
)
|
||||
|
||||
@ -128,7 +132,9 @@
|
||||
_id: generateId(),
|
||||
collection: 'applications',
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: '' as Ref<Account>
|
||||
modifiedBy: '' as Ref<Account>,
|
||||
startDate: null,
|
||||
dueDate: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"StartDate": "Start date",
|
||||
"DueDate": "Due date",
|
||||
"TaskState": "State",
|
||||
"TaskStateTitle": "Title",
|
||||
"TaskStateDone": "Done",
|
||||
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"StartDate": "Начало",
|
||||
"DueDate": "Срок",
|
||||
"TaskState": "Статус",
|
||||
"TaskStateTitle": "Заголовок",
|
||||
"TaskStateDone": "Завершен",
|
||||
|
@ -81,7 +81,8 @@ export interface Task extends AttachedDoc, DocWithRank {
|
||||
doneState: Ref<DoneState> | null
|
||||
number: number
|
||||
assignee: Ref<Employee> | null
|
||||
|
||||
dueDate: Timestamp | null
|
||||
startDate: Timestamp | null
|
||||
todoItems?: number
|
||||
}
|
||||
|
||||
@ -196,6 +197,34 @@ const task = plugin(taskId, {
|
||||
interface: {
|
||||
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: {
|
||||
Issue: '' as Ref<Class<Issue>>,
|
||||
Project: '' as Ref<Class<Project>>,
|
||||
@ -239,9 +268,6 @@ const task = plugin(taskId, {
|
||||
KanbanTemplateEditor: '' as AnyComponent,
|
||||
KanbanTemplateSelector: '' as AnyComponent,
|
||||
TodoItemsPopup: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
TodoItems: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user