platform/plugins/time-resources/src/components/CreateToDoPopup.svelte
Alexey Zinoviev 0fc0115c99
UBERF-7495: Global editor kit extensions (#6057)
Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
2024-07-12 15:42:13 +07:00

313 lines
9.0 KiB
Svelte

<!--
// Copyright © 2023 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 core, { AttachedData, Doc, Ref, SortingOrder, generateId, getCurrentAccount } from '@hcengineering/core'
import { Button, Component, EditBox, IconClose, Label, Scroller } from '@hcengineering/ui'
import { SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
import { ToDo, ToDoPriority, WorkSlot } from '@hcengineering/time'
import { Calendar, generateEventId } from '@hcengineering/calendar'
import tagsPlugin, { TagReference } from '@hcengineering/tags'
import { createEventDispatcher } from 'svelte'
import { VisibilityEditor } from '@hcengineering/calendar-resources'
import { StyledTextBox } from '@hcengineering/text-editor-resources'
import { PersonAccount } from '@hcengineering/contact'
import calendar from '@hcengineering/calendar-resources/src/plugin'
import task, { makeRank } from '@hcengineering/task'
import PriorityEditor from './PriorityEditor.svelte'
import DueDateEditor from './DueDateEditor.svelte'
import Workslots from './Workslots.svelte'
import time from '../plugin'
export let object: Doc | undefined
const acc = getCurrentAccount() as PersonAccount
const todo: AttachedData<ToDo> = {
workslots: 0,
title: '',
description: '',
priority: ToDoPriority.NoPriority,
attachedSpace: object?.space,
visibility: 'private',
user: acc.person,
rank: ''
}
const dispatch = createEventDispatcher()
const client = getClient()
export function canClose (): boolean {
return true
}
let loading = false
async function saveToDo (): Promise<void> {
loading = true
const ops = client.apply('todo-' + generateId())
const latestTodo = await ops.findOne(
time.class.ToDo,
{
user: acc.person,
doneOn: null
},
{
sort: { rank: SortingOrder.Ascending }
}
)
const id = await ops.addCollection(
time.class.ToDo,
time.space.ToDos,
object?._id ?? time.ids.NotAttached,
object?._class ?? time.class.ToDo,
'todos',
{
workslots: 0,
title: todo.title,
description: todo.description,
priority: todo.priority,
visibility: todo.visibility,
user: acc.person,
dueDate: todo.dueDate,
attachedSpace: todo.attachedSpace,
rank: makeRank(undefined, latestTodo?.rank)
}
)
for (const slot of slots) {
await ops.addCollection(time.class.WorkSlot, calendar.space.Calendar, id, time.class.ToDo, 'workslots', {
eventId: generateEventId(),
date: slot.date,
dueDate: slot.dueDate,
description: todo.description,
participants: [acc.person],
calendar: _calendar,
title: todo.title,
allDay: false,
access: 'owner',
visibility: todo.visibility === 'public' ? 'public' : 'freeBusy',
reminders: []
})
}
for (const tag of tags) {
await ops.addCollection(tagsPlugin.class.TagReference, time.space.ToDos, id, time.class.ToDo, 'labels', tag)
}
await ops.commit()
dispatch('close', true)
}
const currentUser = getCurrentAccount() as PersonAccount
let _calendar: Ref<Calendar> = `${currentUser._id}_calendar` as Ref<Calendar>
const q = createQuery()
q.query(calendar.class.ExternalCalendar, { default: true, hidden: false, createdBy: currentUser._id }, (res) => {
if (res.length > 0) {
_calendar = res[0]._id
}
})
let slots: WorkSlot[] = []
function removeSlot (e: CustomEvent<{ _id: Ref<WorkSlot> }>): void {
const index = slots.findIndex((p) => p._id === e.detail._id)
if (index !== -1) {
slots.splice(index, 1)
slots = slots
}
}
function createSlot (): void {
const defaultDuration = 30 * 60 * 1000
const now = Date.now()
const date = Math.ceil(now / (30 * 60 * 1000)) * (30 * 60 * 1000)
const dueDate = date + defaultDuration
slots.push({
eventId: generateEventId(),
date,
dueDate,
description: todo.description,
participants: [acc.person],
title: todo.title,
allDay: false,
access: 'owner',
visibility: todo.visibility,
reminders: [],
calendar: _calendar,
space: calendar.space.Calendar,
_id: generateId(),
_class: time.class.WorkSlot,
attachedTo: generateId(),
attachedToClass: time.class.ToDo,
collection: 'workslots',
modifiedOn: Date.now(),
modifiedBy: acc._id
})
slots = slots
}
function changeSlot (e: CustomEvent<{ startDate: number, dueDate: number, slot: Ref<WorkSlot> }>): void {
const { startDate, dueDate, slot } = e.detail
const workslot = slots.find((s) => s._id === slot)
if (workslot !== undefined) {
workslot.dueDate = dueDate
workslot.date = startDate
slots = slots
}
}
function changeDueSlot (e: CustomEvent<{ dueDate: number, slot: Ref<WorkSlot> }>): void {
const { dueDate, slot } = e.detail
const workslot = slots.find((s) => s._id === slot)
if (workslot !== undefined) {
workslot.dueDate = dueDate
slots = slots
}
}
let tags: AttachedData<TagReference>[] = []
</script>
<div class="eventPopup-container">
<div class="header flex-between">
<EditBox
bind:value={todo.title}
kind={'ghost-large'}
placeholder={time.string.AddTitle}
fullSize
focusable
autoFocus
focusIndex={10001}
/>
<div class="flex-row-center gap-1 flex-no-shrink ml-3">
<Button
id="card-close"
icon={IconClose}
kind={'ghost'}
size={'small'}
on:click={() => {
dispatch('close')
}}
/>
</div>
</div>
<Scroller>
<div class="block flex-no-shrink">
<div class="pb-4">
<StyledTextBox
alwaysEdit={true}
maxHeight="limited"
showButtons={false}
placeholder={calendar.string.Description}
bind:content={todo.description}
/>
</div>
<div class="flex-row-center gap-1-5 mb-1">
<PriorityEditor bind:value={todo.priority} />
<VisibilityEditor size="small" bind:value={todo.visibility} />
<DueDateEditor bind:value={todo.dueDate} />
</div>
</div>
<div class="block flex-no-shrink">
<div class="flex-row-center gap-1-5 mb-1">
<div>
<Label label={time.string.AddTo} />
</div>
<SpaceSelector
_class={task.class.Project}
query={{ archived: false, members: getCurrentAccount()._id }}
label={core.string.Space}
autoSelect={false}
allowDeselect
kind={'regular'}
size={'medium'}
focus={false}
bind:space={todo.attachedSpace}
/>
</div>
</div>
<div class="block flex-no-shrink">
<div class="flex-row-center gap-1-5 mb-1">
<Component
is={tagsPlugin.component.DraftTagsEditor}
props={{ tags, targetClass: time.class.ToDo }}
on:update={(e) => {
tags = [...e.detail]
}}
/>
</div>
</div>
<div class="block flex-gap-4 flex-no-shrink end">
<Workslots
bind:slots
shortcuts={false}
fixed={'createToDo'}
on:remove={removeSlot}
on:create={createSlot}
on:change={changeSlot}
on:dueChange={changeDueSlot}
/>
</div>
<div class="flex-row-reverse btn flex-no-shrink">
<Button
kind="primary"
{loading}
label={time.string.AddToDo}
on:click={saveToDo}
disabled={todo?.title === undefined || todo?.title === ''}
/>
</div>
</Scroller>
</div>
<style lang="scss">
.eventPopup-container {
display: flex;
flex-direction: column;
padding-top: 2rem;
padding-bottom: 2rem;
max-width: 40rem;
min-width: 40rem;
min-height: 0;
background: var(--theme-popup-color);
border-radius: 1rem;
box-shadow: var(--theme-popup-shadow);
.header {
flex-shrink: 0;
padding-right: 2rem;
padding-left: 2rem;
}
.btn {
padding-top: 1rem;
padding-right: 1rem;
padding-left: 1rem;
}
.block {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
padding-top: 1rem;
padding-bottom: 1rem;
padding-right: 2rem;
padding-left: 2rem;
&:not(.end) {
border-bottom: 1px solid var(--theme-divider-color);
}
}
}
</style>