mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-27 21:43:20 +03:00
Reminders (#1200)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
e875f51ad3
commit
4188d6a32b
@ -34,6 +34,7 @@
|
||||
"@anticrm/calendar": "~0.6.0",
|
||||
"@anticrm/calendar-resources": "~0.6.0",
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
"@anticrm/notification": "~0.6.0",
|
||||
"@anticrm/model-core": "~0.6.0",
|
||||
"@anticrm/model-view": "~0.6.0",
|
||||
"@anticrm/model-workbench": "~0.6.1",
|
||||
|
@ -13,19 +13,21 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Calendar, Event } from '@anticrm/calendar'
|
||||
import activity from '@anticrm/activity'
|
||||
import { Calendar, Event, Reminder } from '@anticrm/calendar'
|
||||
import { Employee } from '@anticrm/contact'
|
||||
import type { Domain, Markup, Ref, Timestamp } from '@anticrm/core'
|
||||
import { IndexKind } from '@anticrm/core'
|
||||
import { Builder, Collection, Index, Model, Prop, TypeDate, TypeMarkup, TypeString, UX } from '@anticrm/model'
|
||||
import { Builder, Collection, Index, Mixin, Model, Prop, TypeDate, TypeMarkup, TypeString, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import core, { TAttachedDoc } from '@anticrm/model-core'
|
||||
import { TSpaceWithStates } from '@anticrm/model-task'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import calendar from './plugin'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import notification from '@anticrm/notification'
|
||||
import calendar from './plugin'
|
||||
|
||||
export * from '@anticrm/calendar'
|
||||
|
||||
@ -42,9 +44,6 @@ export class TEvent extends TAttachedDoc implements Event {
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
|
||||
@Prop(TypeString(), calendar.string.EventNumber)
|
||||
number!: number
|
||||
|
||||
@Prop(TypeMarkup(), calendar.string.Description)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: Markup
|
||||
@ -69,8 +68,19 @@ export class TEvent extends TAttachedDoc implements Event {
|
||||
participants!: Ref<Employee>[]
|
||||
}
|
||||
|
||||
@Mixin(calendar.mixin.Reminder, calendar.class.Event)
|
||||
@UX(calendar.string.Reminder, calendar.icon.Calendar)
|
||||
export class TReminder extends TEvent implements Reminder {
|
||||
@Prop(TypeDate(true), calendar.string.Shift)
|
||||
shift!: Timestamp
|
||||
|
||||
@Prop(TypeString(), calendar.string.State)
|
||||
@Index(IndexKind.Indexed)
|
||||
state!: 'active' | 'done'
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TCalendar, TEvent)
|
||||
builder.createModel(TCalendar, TEvent, TReminder)
|
||||
|
||||
builder.createDoc(workbench.class.Application, core.space.Model, {
|
||||
label: calendar.string.ApplicationLabelCalendar,
|
||||
@ -88,6 +98,21 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
}, calendar.app.Calendar)
|
||||
|
||||
builder.createDoc(notification.class.NotificationType, core.space.Model, {
|
||||
label: calendar.string.Reminder
|
||||
}, calendar.ids.ReminderNotification)
|
||||
|
||||
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
|
||||
objectClass: calendar.mixin.Reminder,
|
||||
icon: calendar.icon.Reminder,
|
||||
txClass: core.class.TxMixin,
|
||||
label: calendar.string.CreatedReminder,
|
||||
component: calendar.activity.ReminderViewlet,
|
||||
display: 'emphasized',
|
||||
editable: false,
|
||||
hideOnRemove: true
|
||||
}, calendar.ids.ReminderViewlet)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
@ -99,6 +124,30 @@ export function createModel (builder: Builder): void {
|
||||
calendar.viewlet.Calendar
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: calendar.string.RemindMeAt,
|
||||
icon: calendar.icon.Reminder,
|
||||
action: calendar.actionImpl.SaveEventReminder
|
||||
},
|
||||
calendar.action.SaveEventReminder
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: calendar.class.Event,
|
||||
action: calendar.action.SaveEventReminder
|
||||
})
|
||||
|
||||
builder.mixin(calendar.mixin.Reminder, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: calendar.component.ReminderPresenter
|
||||
})
|
||||
|
||||
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: calendar.component.EditEvent
|
||||
})
|
||||
|
||||
// Use generic child presenter
|
||||
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: view.component.ObjectPresenter
|
||||
|
@ -13,24 +13,40 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { TxViewlet } from '@anticrm/activity'
|
||||
import { calendarId } from '@anticrm/calendar'
|
||||
import calendar from '@anticrm/calendar-resources/src/plugin'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Doc, Ref } from '@anticrm/core'
|
||||
import type { IntlString, Resource } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { ViewletDescriptor } from '@anticrm/view'
|
||||
import { Action, ViewletDescriptor } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(calendarId, calendar, {
|
||||
component: {
|
||||
CreateCalendar: '' as AnyComponent,
|
||||
CalendarView: '' as AnyComponent
|
||||
CalendarView: '' as AnyComponent,
|
||||
EditEvent: '' as AnyComponent,
|
||||
ReminderPresenter: '' as AnyComponent
|
||||
},
|
||||
action: {
|
||||
SaveEventReminder: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
SaveEventReminder: '' as Resource<(object: Doc) => Promise<void>>
|
||||
},
|
||||
string: {
|
||||
ApplicationLabelCalendar: '' as IntlString,
|
||||
Event: '' as IntlString
|
||||
Event: '' as IntlString,
|
||||
Reminder: '' as IntlString,
|
||||
Shift: '' as IntlString,
|
||||
State: '' as IntlString,
|
||||
CreatedReminder: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
Calendar: '' as Ref<ViewletDescriptor>
|
||||
},
|
||||
ids: {
|
||||
ReminderViewlet: '' as Ref<TxViewlet>
|
||||
}
|
||||
})
|
||||
|
@ -13,12 +13,14 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type {
|
||||
import {
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Data,
|
||||
Doc,
|
||||
DocumentUpdate,
|
||||
DOMAIN_TX,
|
||||
IndexKind,
|
||||
Mixin,
|
||||
MixinUpdate,
|
||||
PropertyType,
|
||||
@ -34,8 +36,7 @@ import type {
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc
|
||||
} from '@anticrm/core'
|
||||
import { DOMAIN_TX } from '@anticrm/core'
|
||||
import { Model } from '@anticrm/model'
|
||||
import { Index, Model } from '@anticrm/model'
|
||||
import core from './component'
|
||||
import { TDoc } from './core'
|
||||
|
||||
@ -48,7 +49,9 @@ export class TTx extends TDoc implements Tx {
|
||||
|
||||
@Model(core.class.TxCUD, core.class.Tx)
|
||||
export class TTxCUD<T extends Doc> extends TTx implements TxCUD<T> {
|
||||
@Index(IndexKind.Indexed)
|
||||
objectId!: Ref<T>
|
||||
|
||||
objectClass!: Ref<Class<T>>
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,9 @@ export class TReview extends TEvent implements Review {
|
||||
@Prop(TypeRef(recruit.mixin.Candidate), recruit.string.Candidate)
|
||||
declare attachedTo: Ref<Candidate>
|
||||
|
||||
@Prop(TypeString(), recruit.string.Review)
|
||||
number!: number
|
||||
|
||||
@Prop(TypeString(), recruit.string.Verdict)
|
||||
@Index(IndexKind.FullText)
|
||||
verdict!: string
|
||||
|
@ -140,7 +140,7 @@ export abstract class MemDb extends TxProcessor {
|
||||
result = this.getObjectsByClass(baseClass)
|
||||
}
|
||||
|
||||
result = matchQuery(result, query)
|
||||
result = matchQuery(result, query, _class, this.hierarchy)
|
||||
|
||||
if (baseClass !== _class) {
|
||||
// We need to filter instances without mixin was set
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DocumentQuery } from '.'
|
||||
import { Doc } from './classes'
|
||||
import { Class, Doc, Ref } from './classes'
|
||||
import { Hierarchy } from './hierarchy'
|
||||
import { getObjectValue } from './objvalue'
|
||||
import { createPredicates, isPredicate } from './predicate'
|
||||
import { SortingOrder, SortingQuery } from './storage'
|
||||
@ -71,15 +72,31 @@ function getValue (key: string, obj: any): any {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function matchQuery<T extends Doc> (docs: Doc[], query: DocumentQuery<T>): Doc[] {
|
||||
export function matchQuery<T extends Doc> (docs: Doc[], query: DocumentQuery<T>, clazz: Ref<Class<T>>, hierarchy: Hierarchy): Doc[] {
|
||||
let result = [...docs]
|
||||
for (const key in query) {
|
||||
if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue
|
||||
const value = (query as any)[key]
|
||||
result = findProperty(result, key, value)
|
||||
const tkey = checkMixinKey(key, clazz, hierarchy)
|
||||
result = findProperty(result, tkey, value)
|
||||
if (result.length === 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function checkMixinKey<T extends Doc> (key: string, clazz: Ref<Class<T>>, hierarchy: Hierarchy): string {
|
||||
if (!key.includes('.')) {
|
||||
try {
|
||||
const attr = hierarchy.getAttribute(clazz, key)
|
||||
if (hierarchy.isMixin(attr.attributeOf)) {
|
||||
// It is mixin
|
||||
key = attr.attributeOf + '.' + key
|
||||
}
|
||||
} catch (err: any) {
|
||||
// ignore, if
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
"@anticrm/chunter": "~0.6.1",
|
||||
"@anticrm/presentation": "~0.6.2",
|
||||
"@anticrm/activity": "~0.6.0",
|
||||
"@anticrm/calendar": "~0.6.0",
|
||||
"@anticrm/notification": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
import activity from '@anticrm/activity'
|
||||
import type { Doc } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import calendar from '@anticrm/calendar'
|
||||
import type { Asset } from '@anticrm/platform'
|
||||
import { ActionIcon,AnyComponent,AnySvelteComponent,Component,Icon,IconClose,IconExpand,IconMoreH,Scroller } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@ -43,7 +44,12 @@
|
||||
{#if subtitle }<span class="ac-header__description">{subtitle}</span>{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
|
||||
<div class="flex">
|
||||
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
|
||||
<div class="ml-2">
|
||||
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if $$slots.subtitle}
|
||||
<div class="ac-subtitle">
|
||||
|
@ -25,7 +25,7 @@
|
||||
import presentation from '..'
|
||||
|
||||
export let spaceClass: Ref<Class<Space>> | undefined = undefined
|
||||
export let space: Ref<Space>
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
|
||||
export let spaceLabel: IntlString | undefined = undefined
|
||||
export let spacePlaceholder: IntlString | undefined = undefined
|
||||
|
@ -26,7 +26,7 @@
|
||||
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
|
||||
export let label: IntlString
|
||||
export let placeholder: IntlString
|
||||
export let value: Ref<Space>
|
||||
export let value: Ref<Space> | undefined
|
||||
export let show: boolean = false
|
||||
|
||||
let selected: Space | undefined
|
||||
@ -34,8 +34,8 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function updateSelected (value: Ref<Space>) {
|
||||
selected = await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value })
|
||||
async function updateSelected (value: Ref<Space> | undefined) {
|
||||
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
|
||||
}
|
||||
|
||||
$: updateSelected(value)
|
||||
|
@ -57,6 +57,10 @@
|
||||
}}
|
||||
on:blur={() => {
|
||||
focused = false
|
||||
if (alwaysEdit) {
|
||||
dispatch('value', rawValue)
|
||||
content = rawValue
|
||||
}
|
||||
}}
|
||||
on:value={(evt) => {
|
||||
rawValue = evt.detail
|
||||
|
@ -17,7 +17,11 @@
|
||||
"Today": "Today",
|
||||
"English": "English",
|
||||
"Russian": "Russian",
|
||||
"CalendarLeft": "<",
|
||||
"CalendarRight": ">"
|
||||
"MinutesBefore": "{minutes, plural, =1 {a minute before} other {# minutes before}}",
|
||||
"HoursBefore": "{hours, plural, =1 {an hour before} other {# hours before}}",
|
||||
"DaysBefore": "{days, plural, =1 {a day before} other {# days before}}",
|
||||
"MinutesAfter": "{minutes, plural, =1 {in a minute} other {in # minutes}}",
|
||||
"HoursAfter": "{hours, plural, =1 {in an hour} other {in # hours}}",
|
||||
"DaysAfter": "{days, plural, =1 {in a day} other {in # days}}"
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,11 @@
|
||||
"Today": "Сегодня",
|
||||
"English": "Английский",
|
||||
"Russian": "Русский",
|
||||
"CalendarLeft": "<",
|
||||
"CalendarRight": ">"
|
||||
"MinutesBefore": "{minutes, plural, =1 {за минуту} other {за # минут}}",
|
||||
"HoursBefore": "{hours, plural, =1 {за час} other {за # часа}}",
|
||||
"DaysBefore": "{days, plural, =1 {за день} =3 {за # дня} other {за # дней}}",
|
||||
"MinutesAfter": "{minutes, plural, =1 {через минуту} other {через # минут}}",
|
||||
"HoursAfter": "{hours, plural, =1 {через час} other {через # часа}}",
|
||||
"DaysAfter": "{days, plural, =1 {через день} other {через # дней}}"
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
export let direction: TooltipAligment | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let action: (ev?: Event) => Promise<void> | void = async () => { }
|
||||
export let action: (ev: Event) => Promise<void> | void = async () => { }
|
||||
export let invisible: boolean = false
|
||||
</script>
|
||||
|
||||
|
79
packages/ui/src/components/TimeShiftPicker.svelte
Normal file
79
packages/ui/src/components/TimeShiftPicker.svelte
Normal file
@ -0,0 +1,79 @@
|
||||
<!--
|
||||
// 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 { IntlString } from '@anticrm/platform'
|
||||
import { createEventDispatcher,onMount } from 'svelte'
|
||||
import { Label,showPopup } from '..'
|
||||
import Calendar from './icons/Calendar.svelte'
|
||||
import Close from './icons/Close.svelte'
|
||||
import TimeShiftPopup from './TimeShiftPopup.svelte'
|
||||
import TimeShiftPresenter from './TimeShiftPresenter.svelte'
|
||||
|
||||
export let title: IntlString
|
||||
export let value: number
|
||||
export let show: boolean = false
|
||||
export let direction: 'before' | 'after' = 'before'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let opened: boolean = false
|
||||
let container: HTMLElement
|
||||
let btn: HTMLElement
|
||||
|
||||
const changeValue = (result: any): void => {
|
||||
if (result !== undefined) {
|
||||
value = result
|
||||
dispatch('change', result)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (btn && show) {
|
||||
btn.click()
|
||||
show = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="antiSelect"
|
||||
bind:this={container}
|
||||
on:click|preventDefault={() => {
|
||||
btn.focus()
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(TimeShiftPopup, { title, value, direction }, container, (ev) => {
|
||||
console.log('picker close handle')
|
||||
console.log(ev)
|
||||
changeValue(ev)
|
||||
opened = false
|
||||
}, changeValue)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
bind:this={btn}
|
||||
class="button round-2"
|
||||
class:selected={value}
|
||||
>
|
||||
<div class="icon">
|
||||
{#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="group">
|
||||
<span class="label"><Label label={title} /></span>
|
||||
<TimeShiftPresenter {value} />
|
||||
</div>
|
||||
</div>
|
40
packages/ui/src/components/TimeShiftPopup.svelte
Normal file
40
packages/ui/src/components/TimeShiftPopup.svelte
Normal file
@ -0,0 +1,40 @@
|
||||
<!--
|
||||
// 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 { createEventDispatcher } from 'svelte'
|
||||
import TimeShiftPresenter from './TimeShiftPresenter.svelte'
|
||||
|
||||
export let value: number
|
||||
export let direction: 'before' | 'after'
|
||||
export let minutes: number[] = [5, 15, 30]
|
||||
export let hours: number[] = [1, 2, 4]
|
||||
export let days: number[] = [1, 3, 7, 30]
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: base = direction === 'before' ? -1 : 1
|
||||
const MINUTE = 60 * 1000
|
||||
const HOUR = 60 * MINUTE
|
||||
const DAY = 24 * HOUR
|
||||
$: values = [...minutes.map((m) => m * MINUTE), ...hours.map((m) => m * HOUR), ...days.map((m) => m * DAY)]
|
||||
|
||||
</script>
|
||||
|
||||
<div class="antiPopup">
|
||||
{#each values as value}
|
||||
<div class="ap-menuItem">
|
||||
<TimeShiftPresenter value={value * base} on:click={() => { dispatch('close', value) }} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
55
packages/ui/src/components/TimeShiftPresenter.svelte
Normal file
55
packages/ui/src/components/TimeShiftPresenter.svelte
Normal file
@ -0,0 +1,55 @@
|
||||
<!--
|
||||
// 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 { translate } from "@anticrm/platform"
|
||||
import ui from '../plugin'
|
||||
|
||||
export let value: number
|
||||
|
||||
const SECOND = 1000
|
||||
const MINUTE = SECOND * 60
|
||||
const HOUR = MINUTE * 60
|
||||
const DAY = HOUR * 24
|
||||
|
||||
let time: string = ''
|
||||
|
||||
async function formatTime (value: number) {
|
||||
console.log('value')
|
||||
console.log(value)
|
||||
if (value > 0) {
|
||||
if (value < HOUR) {
|
||||
time = await translate(ui.string.MinutesAfter, { minutes: Math.floor(value / MINUTE) })
|
||||
} else if (value < DAY) {
|
||||
time = await translate(ui.string.HoursAfter, { hours: Math.floor(value / HOUR) })
|
||||
} else {
|
||||
time = await translate(ui.string.DaysAfter, { days: Math.floor(value / DAY) })
|
||||
}
|
||||
} else {
|
||||
const abs = Math.abs(value)
|
||||
if (abs < HOUR) {
|
||||
time = await translate(ui.string.MinutesBefore, { minutes: Math.floor(abs / MINUTE) })
|
||||
} else if (abs < DAY) {
|
||||
time = await translate(ui.string.HoursBefore, { hours: Math.floor(abs / HOUR) })
|
||||
} else {
|
||||
time = await translate(ui.string.DaysBefore, { days: Math.floor(abs / DAY) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: formatTime(value)
|
||||
|
||||
</script>
|
||||
|
||||
<span style="white-space: nowrap;" on:click>{time}</span>
|
@ -66,6 +66,7 @@ export { default as DropdownPopup } from './components/DropdownPopup.svelte'
|
||||
export { default as DropdownLabels } from './components/DropdownLabels.svelte'
|
||||
export { default as ShowMore } from './components/ShowMore.svelte'
|
||||
export { default as Menu } from './components/Menu.svelte'
|
||||
export { default as TimeShiftPicker } from './components/TimeShiftPicker.svelte'
|
||||
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'
|
||||
export { default as Scroller } from './components/Scroller.svelte'
|
||||
|
||||
|
@ -41,7 +41,13 @@ export default plugin(uiId, {
|
||||
NotSelected: '' as IntlString,
|
||||
Today: '' as IntlString,
|
||||
English: '' as IntlString,
|
||||
Russian: '' as IntlString
|
||||
Russian: '' as IntlString,
|
||||
MinutesBefore: '' as IntlString,
|
||||
HoursBefore: '' as IntlString,
|
||||
DaysBefore: '' as IntlString,
|
||||
MinutesAfter: '' as IntlString,
|
||||
HoursAfter: '' as IntlString,
|
||||
DaysAfter: '' as IntlString
|
||||
},
|
||||
metadata: {
|
||||
DefaultApplication: '' as Metadata<AnyComponent>
|
||||
|
@ -50,6 +50,16 @@ export function getDTxProps (dtx: DisplayTx): any {
|
||||
return { tx: dtx.tx, value: dtx.doc, dtx }
|
||||
}
|
||||
|
||||
function getViewlet (viewlets: Map<ActivityKey, TxViewlet>, dtx: DisplayTx): TxDisplayViewlet | undefined {
|
||||
let key: string
|
||||
if (dtx.mixinTx?.mixin !== undefined && dtx.tx._id === dtx.mixinTx._id) {
|
||||
key = activityKey(dtx.mixinTx.mixin, dtx.tx._class)
|
||||
} else {
|
||||
key = activityKey(dtx.tx.objectClass, dtx.tx._class)
|
||||
}
|
||||
return viewlets.get(key)
|
||||
}
|
||||
|
||||
export async function updateViewlet (
|
||||
client: TxOperations,
|
||||
viewlets: Map<ActivityKey, TxViewlet>,
|
||||
@ -61,8 +71,7 @@ export async function updateViewlet (
|
||||
props: any
|
||||
modelIcon: Asset | undefined
|
||||
}> {
|
||||
const key = activityKey(dtx.tx.objectClass, dtx.tx._class)
|
||||
let viewlet: TxDisplayViewlet = viewlets.get(key)
|
||||
let viewlet = getViewlet(viewlets, dtx)
|
||||
|
||||
const props = getDTxProps(dtx)
|
||||
let model: AttributeModel[] = []
|
||||
|
@ -6,4 +6,7 @@
|
||||
<path d="M8,4.6c-1.5,0-2.6,1.2-2.6,2.6S6.5,9.9,8,9.9s2.6-1.2,2.6-2.6S9.5,4.6,8,4.6z M8,8.9c-0.9,0-1.6-0.7-1.6-1.6 c0-0.9,0.7-1.6,1.6-1.6s1.6,0.7,1.6,1.6C9.6,8.2,8.9,8.9,8,8.9z"/>
|
||||
<path d="M8,1.8c-3,0-5.5,2.5-5.5,5.5c0,1.9,0.9,3.4,2,4.5c1.1,1.1,2.3,1.8,2.9,2.1c0.4,0.2,0.9,0.2,1.3,0c0.6-0.3,1.8-1,2.9-2.1 c1.1-1.1,2-2.6,2-4.5C13.5,4.3,11,1.8,8,1.8z M10.8,11.1c-1,1-2.1,1.7-2.6,2c-0.1,0.1-0.2,0.1-0.3,0c-0.6-0.3-1.7-1-2.6-2 c-1-1-1.7-2.3-1.7-3.8c0-2.5,2-4.5,4.5-4.5s4.5,2,4.5,4.5C12.5,8.8,11.7,10.1,10.8,11.1z"/>
|
||||
</symbol>
|
||||
<symbol id="reminder" viewBox="0 0 493 511.92">
|
||||
<path fill-rule="nonzero" d="M277.16 41.75c49.87 6.77 94.55 29.88 128.47 63.79 40.67 40.67 65.83 96.87 65.83 158.93 0 62.08-25.15 118.28-65.83 158.96a227.22 227.22 0 0 1-25.34 21.83l27.24 38.33c5.68 8.18 3.65 19.42-4.54 25.11-8.19 5.68-19.44 3.65-25.12-4.54l-28.28-39.78c-30.84 15.91-65.83 24.89-102.92 24.89-37.7 0-73.23-9.28-104.43-25.69l-26.59 39.71c-5.54 8.28-16.76 10.5-25.04 4.95-8.29-5.54-10.5-16.75-4.95-25.03l26.07-38.95a225.636 225.636 0 0 1-24-20.83c-40.68-40.68-65.84-96.89-65.84-158.96 0-62.07 25.16-118.26 65.84-158.94 36.44-36.43 85.34-60.39 139.74-65.03 16.45-1.4 33.38-.96 49.69 1.25zm204.53 102.98c17.3-41.28 15.24-84.52-9.51-113.49-29.7-34.77-83.39-38.75-133.26-14.3 53.01 36.36 101.12 78.78 142.77 127.79zm-470.15 1.35C-6.1 104.02-4.01 59.97 21.21 30.45 51.47-4.97 106.18-9.03 156.99 15.88c-54 37.06-103.03 80.26-145.45 130.2zm269.3 101.47 67.65-1.18c9.97-.17 18.19 7.76 18.36 17.73.18 9.97-7.76 18.19-17.73 18.37l-69.51 1.21c-6.61 11.32-18.89 18.93-32.94 18.93-21.05 0-38.12-17.08-38.12-38.13 0-14.52 8.13-27.15 20.08-33.58v-87.35c0-9.97 8.07-18.05 18.04-18.05 9.97 0 18.06 8.08 18.06 18.05v87.35a38.324 38.324 0 0 1 16.11 16.65zm99.27-116.5c-34.14-34.14-81.32-55.26-133.43-55.26-52.1 0-99.28 21.12-133.42 55.26-34.15 34.14-55.27 81.32-55.27 133.43 0 52.11 21.12 99.28 55.27 133.43 34.14 34.14 81.31 55.26 133.41 55.26 52.12 0 99.29-21.12 133.43-55.26 34.14-34.15 55.28-81.32 55.28-133.44 0-52.1-21.13-99.27-55.27-133.42z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.6 KiB |
@ -25,6 +25,13 @@
|
||||
"TableView": "Table",
|
||||
"DueMinutes": "{minutes, plural, =0 {less than a minute} =1 {a minute} other {# minutes}}",
|
||||
"DueHours": "{hours, plural, =0 {less than an hour} =1 {1 hour} other {# hours}}",
|
||||
"DueDays": "{days, plural, =0 {today} =1 {1 day} other {# days}}"
|
||||
"DueDays": "{days, plural, =0 {today} =1 {1 day} other {# days}}",
|
||||
"Reminder": "Reminder",
|
||||
"ReminderTime": "Reminder time",
|
||||
"RemindMeAt": "Remind me at",
|
||||
"EditReminder": "Edit reminder",
|
||||
"CreateReminder": "Create reminder",
|
||||
"CreatedReminder": "Created a reminder",
|
||||
"Reminders": "Reminders"
|
||||
}
|
||||
}
|
@ -25,6 +25,13 @@
|
||||
"TableView": "Таблица",
|
||||
"DueMinutes": "{minutes, plural, =0 {меньше минуты} =1 {минута} other {# минут}}",
|
||||
"DueHours": "{hours, plural, =0 {меньше часа} =1 {1 час} other {# часы}}",
|
||||
"DueDays": "{days, plural, =0 {сегодня} =1 {1 день} other {# дня}}"
|
||||
"DueDays": "{days, plural, =0 {сегодня} =1 {1 день} other {# дня}}",
|
||||
"Reminder": "Напоминание",
|
||||
"RemindMeAt": "Напомнить мне",
|
||||
"ReminderTime": "Время напоминания",
|
||||
"EditReminder": "Редактировать напоминание",
|
||||
"CreateReminder": "Создать напоминание",
|
||||
"CreatedReminder": "Создал напоминание",
|
||||
"Reminders": "Напоминания"
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import calendar, { calendarId } from '@anticrm/calendar'
|
||||
const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(calendar.icon, {
|
||||
Calendar: `${icons}#calendar`,
|
||||
Reminder: `${icons}#reminder`,
|
||||
Location: `${icons}#location`
|
||||
})
|
||||
|
||||
|
@ -0,0 +1,91 @@
|
||||
<!--
|
||||
// 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 contact,{ Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import { Class,Doc,getCurrentAccount,Ref } from '@anticrm/core'
|
||||
import { Card,getClient,UserBoxList } from '@anticrm/presentation'
|
||||
import { TimeShiftPicker,Grid,StylishEdit } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let attachedTo: Ref<Doc>
|
||||
export let attachedToClass: Ref<Class<Doc>>
|
||||
export let title: string = ''
|
||||
|
||||
let shift = 30 * 60 * 1000
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
let participants: Ref<Employee>[] = [currentUser.employee]
|
||||
const space = calendar.space.PersonalEvents
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
export function canClose (): boolean {
|
||||
return title.trim().length === 0 && participants.length === 0
|
||||
}
|
||||
|
||||
async function saveReminder () {
|
||||
const date = new Date().getTime() + shift
|
||||
const _id = await client.createDoc(calendar.class.Event, space, {
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
collection: 'reminders',
|
||||
date,
|
||||
description: '',
|
||||
participants,
|
||||
title
|
||||
})
|
||||
|
||||
await client.createMixin(_id, calendar.class.Event, space, calendar.mixin.Reminder, {
|
||||
shift: 0,
|
||||
state: 'active'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={calendar.string.CreateReminder}
|
||||
okAction={saveReminder}
|
||||
canSave={title.trim().length > 0 && participants.length > 0}
|
||||
{space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
<StylishEdit bind:value={title} label={calendar.string.Title} />
|
||||
<div class="antiComponentBox">
|
||||
<TimeShiftPicker title={calendar.string.Date} bind:value={shift} direction='after' />
|
||||
</div>
|
||||
<UserBoxList
|
||||
_class={contact.class.Employee}
|
||||
items={participants}
|
||||
title={calendar.string.Participants}
|
||||
on:open={(evt) => {
|
||||
participants.push(evt.detail._id)
|
||||
participants = participants
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
const _id = evt.detail._id
|
||||
const index = participants.findIndex((p) => p === _id)
|
||||
if (index !== -1) {
|
||||
participants.splice(index, 1)
|
||||
participants = participants
|
||||
}
|
||||
}}
|
||||
noItems={calendar.string.NoParticipants}
|
||||
/>
|
||||
</Grid>
|
||||
</Card>
|
52
plugins/calendar-resources/src/components/DocReminder.svelte
Normal file
52
plugins/calendar-resources/src/components/DocReminder.svelte
Normal file
@ -0,0 +1,52 @@
|
||||
<!--
|
||||
// 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 { EmployeeAccount } from '@anticrm/contact'
|
||||
import { Doc,getCurrentAccount } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { ActionIcon,showPopup } from '@anticrm/ui'
|
||||
import calendar from '../plugin'
|
||||
import CreateReminder from './CreateReminder.svelte'
|
||||
import DocRemindersPopup from './DocRemindersPopup.svelte'
|
||||
import SaveEventReminder from './SaveEventReminder.svelte'
|
||||
|
||||
export let value: Doc
|
||||
export let title: string | undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: isEvent = hierarchy.isDerived(value._class, calendar.class.Event)
|
||||
|
||||
async function click (ev: Event): Promise<void> {
|
||||
if (isEvent) {
|
||||
showPopup(SaveEventReminder, { objectId: value._id, objectClass: value._class }, ev.target as HTMLElement)
|
||||
} else {
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
const current = await client.findOne(calendar.mixin.Reminder, { attachedTo: value._id, state: 'active', participants: currentUser.employee })
|
||||
if (current === undefined) {
|
||||
showPopup(CreateReminder, { attachedTo: value._id, attachedToClass: value._class, title }, ev.target as HTMLElement)
|
||||
} else {
|
||||
showPopup(DocRemindersPopup, { attachedTo: value._id, attachedToClass: value._class }, ev.target as HTMLElement )
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isEvent}
|
||||
<ActionIcon size='medium' label={calendar.string.RemindMeAt} icon={calendar.icon.Reminder} action={(e) => click(e)} />
|
||||
{:else}
|
||||
<ActionIcon size='medium' label={calendar.string.Reminders} icon={calendar.icon.Reminder} action={(e) => click(e)} />
|
||||
{/if}
|
@ -0,0 +1,50 @@
|
||||
<!--
|
||||
// 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 { EmployeeAccount } from '@anticrm/contact'
|
||||
import { Class,Doc,getCurrentAccount,Ref } from '@anticrm/core'
|
||||
import { Button,showPopup } from '@anticrm/ui'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import calendar from '../plugin'
|
||||
import CreateReminder from './CreateReminder.svelte'
|
||||
|
||||
export let attachedTo: Ref<Doc>
|
||||
export let attachedToClass: Ref<Class<Doc>>
|
||||
export let title: string | undefined
|
||||
|
||||
function click (ev: Event): void {
|
||||
showPopup(CreateReminder, { attachedTo, attachedToClass, title }, ev.target as HTMLElement)
|
||||
}
|
||||
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
</script>
|
||||
|
||||
<div class='antiPopup'>
|
||||
<Button label={calendar.string.CreateReminder} primary on:click={(e) => click(e)} />
|
||||
<div class="ap-space" />
|
||||
<Table
|
||||
_class={calendar.mixin.Reminder}
|
||||
config={['']}
|
||||
options={ {} }
|
||||
query={ { attachedTo, state: 'active', participants: currentUser.employee } }
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.antiPopup {
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
75
plugins/calendar-resources/src/components/EditEvent.svelte
Normal file
75
plugins/calendar-resources/src/components/EditEvent.svelte
Normal file
@ -0,0 +1,75 @@
|
||||
<!--
|
||||
// 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 { Event } from '@anticrm/calendar'
|
||||
import contact from '@anticrm/contact'
|
||||
import { getClient,UserBoxList } from '@anticrm/presentation'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { Label,StylishEdit } from '@anticrm/ui'
|
||||
import { createEventDispatcher,onMount } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let object: Event
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', {
|
||||
ignoreKeys: ['comments', 'title', 'description'],
|
||||
ignoreMixins: [calendar.mixin.Reminder]
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="mb-2">
|
||||
<div class="mb-4">
|
||||
<StylishEdit
|
||||
label={calendar.string.Title}
|
||||
bind:value={object.title}
|
||||
on:change={() => client.update(object, { title: object.title })}
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<StyledTextBox
|
||||
emphasized
|
||||
content={object.description}
|
||||
on:value={(evt) => {
|
||||
client.update(object, { description: evt.detail })
|
||||
}}
|
||||
label={calendar.string.Description}
|
||||
placeholder={calendar.string.Description}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<div class="mt-4 mb-2">
|
||||
<Label label={calendar.string.Participants} />
|
||||
</div>
|
||||
<UserBoxList
|
||||
_class={contact.class.Employee}
|
||||
items={object.participants}
|
||||
title={calendar.string.Participants}
|
||||
on:open={(evt) => {
|
||||
client.update(object, { $push: { participants: evt.detail._id } })
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
client.update(object, { $pull: { participants: evt.detail._id } })
|
||||
}}
|
||||
noItems={calendar.string.NoParticipants}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
@ -0,0 +1,97 @@
|
||||
<!--
|
||||
// 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 { Event } from '@anticrm/calendar'
|
||||
import contact,{ Employee } from '@anticrm/contact'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import presentation, { Card,getClient,UserBoxList } from '@anticrm/presentation'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import ui, { DatePicker,Grid,StylishEdit } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let value: Event
|
||||
|
||||
let title: string = value.title
|
||||
let description: string = value.description
|
||||
let startDate: Date = new Date(value.date)
|
||||
let participants: Ref<Employee>[] = value.participants ?? []
|
||||
const space = calendar.space.PersonalEvents
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
export function canClose (): boolean {
|
||||
return title.trim().length === 0 && participants.length === 0
|
||||
}
|
||||
|
||||
async function saveReminder () {
|
||||
await client.updateDoc(value._class, value.space, value._id, {
|
||||
date: startDate.getTime(),
|
||||
description,
|
||||
participants,
|
||||
title
|
||||
})
|
||||
|
||||
await client.updateMixin(value._id, value._class, space, calendar.mixin.Reminder, {
|
||||
shift: 0,
|
||||
state: 'active'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={calendar.string.EditReminder}
|
||||
okAction={saveReminder}
|
||||
okLabel={presentation.string.Save}
|
||||
canSave={title.trim().length > 0 && startDate.getTime() > 0 && participants.length > 0}
|
||||
{space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
<StylishEdit bind:value={title} label={calendar.string.Title} />
|
||||
<StyledTextBox
|
||||
emphasized
|
||||
showButtons={false}
|
||||
alwaysEdit
|
||||
bind:content={description}
|
||||
label={calendar.string.Description}
|
||||
placeholder={calendar.string.Description}
|
||||
/>
|
||||
<div class="antiComponentBox">
|
||||
<DatePicker title={calendar.string.Date} bind:value={startDate} withTime />
|
||||
</div>
|
||||
<UserBoxList
|
||||
_class={contact.class.Employee}
|
||||
items={participants}
|
||||
title={calendar.string.Participants}
|
||||
on:open={(evt) => {
|
||||
participants.push(evt.detail._id)
|
||||
participants = participants
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
const _id = evt.detail._id
|
||||
const index = participants.findIndex((p) => p === _id)
|
||||
if (index !== -1) {
|
||||
participants.splice(index, 1)
|
||||
participants = participants
|
||||
}
|
||||
}}
|
||||
noItems={calendar.string.NoParticipants}
|
||||
/>
|
||||
</Grid>
|
||||
</Card>
|
@ -0,0 +1,42 @@
|
||||
<!--
|
||||
// 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 { Reminder } from '@anticrm/calendar'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { DateTimePresenter, showPanel, Tooltip } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Reminder
|
||||
|
||||
function click (): void {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
|
||||
const objectPresenter = getResource(view.component.ObjectPresenter)
|
||||
</script>
|
||||
|
||||
<div class="antiSelect w-full cursor-pointer flex-between" on:click={click}>
|
||||
{#if value}
|
||||
<div class="mr-4">
|
||||
{#await objectPresenter then component}
|
||||
<Tooltip {component} props={{ objectId: value.attachedTo, _class: value.attachedToClass }}>
|
||||
{value.title}
|
||||
</Tooltip>
|
||||
{/await}
|
||||
</div>
|
||||
<DateTimePresenter value={new Date(value.date + value.shift)} />
|
||||
{/if}
|
||||
</div>
|
@ -0,0 +1,38 @@
|
||||
<!--
|
||||
// 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 { EmployeeAccount } from '@anticrm/contact'
|
||||
import { getCurrentAccount, Lookup } from '@anticrm/core'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import calendar from '../plugin'
|
||||
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
</script>
|
||||
|
||||
<div class='antiPopup'>
|
||||
<Table
|
||||
_class={calendar.mixin.Reminder}
|
||||
config={['']}
|
||||
options={{}}
|
||||
query={ { state: 'active', participants: currentUser.employee } }
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.antiPopup {
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,63 @@
|
||||
<!--
|
||||
// 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 { Event } from '@anticrm/calendar'
|
||||
import { Class,Ref, Timestamp } from '@anticrm/core'
|
||||
import presentation, { Card,createQuery,getClient } from '@anticrm/presentation'
|
||||
import { Grid, TimeShiftPicker } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let objectId: Ref<Event>
|
||||
export let objectClass: Ref<Class<Event>>
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
query.query(objectClass, { _id: objectId }, (res) => {
|
||||
event = res[0]
|
||||
})
|
||||
let shift: Timestamp = -30 * 60 * 1000
|
||||
let event: Event | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export function canClose (): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
async function saveReminder () {
|
||||
if (event === undefined) return
|
||||
await client.updateMixin(event._id, event._class, event.space, calendar.mixin.Reminder, {
|
||||
shift,
|
||||
state: 'active'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={calendar.string.RemindMeAt}
|
||||
okLabel={presentation.string.Save}
|
||||
canSave={event !== undefined}
|
||||
okAction={saveReminder}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<Grid column={1} rowGap={1.75}>
|
||||
<div class="antiComponentBox">
|
||||
<TimeShiftPicker title={calendar.string.RemindMeAt} bind:value={shift} />
|
||||
</div>
|
||||
</Grid>
|
||||
</Card>
|
@ -0,0 +1,43 @@
|
||||
<!--
|
||||
// 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 { Event,Reminder } from '@anticrm/calendar'
|
||||
import { Ref,TxMixin } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { DateTimePresenter,showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import calendar from '../../plugin'
|
||||
|
||||
export let tx: TxMixin<Event, Reminder>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function getEvent (_id: Ref<Event>): Promise<Event | undefined> {
|
||||
return await client.findOne(calendar.class.Event, { _id })
|
||||
}
|
||||
|
||||
function click (event: Event): void {
|
||||
showPanel(view.component.EditDoc, event._id, event._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='flex'>
|
||||
{#await getEvent(tx.objectId) then event}
|
||||
{#if event}
|
||||
<span class="over-underline caption-color" on:click={() => { click(event) }}>{event.title}</span> 
|
||||
<DateTimePresenter value={new Date(event.date)} />
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
@ -13,18 +13,39 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Doc } from '@anticrm/core'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
|
||||
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import CalendarView from './components/CalendarView.svelte'
|
||||
import UpcomingEvents from './components/UpcomingEvents.svelte'
|
||||
import SaveEventReminder from './components/SaveEventReminder.svelte'
|
||||
import DateTimePresenter from './components/DateTimePresenter.svelte'
|
||||
import DocReminder from './components/DocReminder.svelte'
|
||||
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
||||
import UpcomingEvents from './components/UpcomingEvents.svelte'
|
||||
import ReminderPresenter from './components/ReminderPresenter.svelte'
|
||||
import ReminderViewlet from './components/activity/ReminderViewlet.svelte'
|
||||
import EditEvent from './components/EditEvent.svelte'
|
||||
import RemindersPopup from './components/RemindersPopup.svelte'
|
||||
|
||||
async function saveEventReminder (object: Doc): Promise<void> {
|
||||
showPopup(SaveEventReminder, { objectId: object._id, objectClass: object._class })
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
EditEvent,
|
||||
ReminderPresenter,
|
||||
PersonsPresenter,
|
||||
CalendarView,
|
||||
UpcomingEvents,
|
||||
DateTimePresenter
|
||||
DateTimePresenter,
|
||||
DocReminder,
|
||||
RemindersPopup
|
||||
},
|
||||
activity: {
|
||||
ReminderViewlet
|
||||
},
|
||||
actionImpl: {
|
||||
SaveEventReminder: saveEventReminder
|
||||
}
|
||||
})
|
||||
|
@ -15,12 +15,21 @@
|
||||
|
||||
import calendar, { calendarId } from '@anticrm/calendar'
|
||||
import { IntlString, mergeIds } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
|
||||
export default mergeIds(calendarId, calendar, {
|
||||
component: {
|
||||
},
|
||||
activity: {
|
||||
ReminderViewlet: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Events: '' as IntlString,
|
||||
RemindMeAt: '' as IntlString,
|
||||
CreateReminder: '' as IntlString,
|
||||
EditReminder: '' as IntlString,
|
||||
ReminderTime: '' as IntlString,
|
||||
Reminders: '' as IntlString,
|
||||
ModeDay: '' as IntlString,
|
||||
ModeWeek: '' as IntlString,
|
||||
ModeMonth: '' as IntlString,
|
||||
|
@ -28,6 +28,7 @@
|
||||
"dependencies": {
|
||||
"@anticrm/platform": "~0.6.5",
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/notification": "~0.6.0",
|
||||
"@anticrm/core": "~0.6.11",
|
||||
"@anticrm/contact": "~0.6.5"
|
||||
}
|
||||
|
@ -12,10 +12,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { Employee } from '@anticrm/contact'
|
||||
import type { AttachedDoc, Class, Doc, Markup, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { NotificationType } from '@anticrm/notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -27,7 +28,6 @@ export interface Calendar extends Space {}
|
||||
*/
|
||||
export interface Event extends AttachedDoc {
|
||||
title: string
|
||||
number: number
|
||||
description: Markup
|
||||
|
||||
location?: string
|
||||
@ -44,6 +44,14 @@ export interface Event extends AttachedDoc {
|
||||
participants?: Ref<Employee>[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Reminder extends Event {
|
||||
shift: Timestamp
|
||||
state: 'active' | 'done'
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -57,9 +65,13 @@ const calendarPlugin = plugin(calendarId, {
|
||||
Calendar: '' as Ref<Class<Calendar>>,
|
||||
Event: '' as Ref<Class<Event>>
|
||||
},
|
||||
mixin: {
|
||||
Reminder: '' as Ref<Mixin<Reminder>>
|
||||
},
|
||||
icon: {
|
||||
Calendar: '' as Asset,
|
||||
Location: '' as Asset
|
||||
Location: '' as Asset,
|
||||
Reminder: '' as Asset
|
||||
},
|
||||
space: {
|
||||
// Space for all personal events.
|
||||
@ -71,7 +83,9 @@ const calendarPlugin = plugin(calendarId, {
|
||||
component: {
|
||||
PersonsPresenter: '' as AnyComponent,
|
||||
UpcomingEvents: '' as AnyComponent,
|
||||
DateTimePresenter: '' as AnyComponent
|
||||
DateTimePresenter: '' as AnyComponent,
|
||||
DocReminder: '' as AnyComponent,
|
||||
RemindersPopup: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Title: '' as IntlString,
|
||||
@ -86,6 +100,9 @@ const calendarPlugin = plugin(calendarId, {
|
||||
NoParticipants: '' as IntlString,
|
||||
PersonsLabel: '' as IntlString,
|
||||
EventNumber: '' as IntlString
|
||||
},
|
||||
ids: {
|
||||
ReminderNotification: '' as Ref<NotificationType>
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -107,6 +107,7 @@
|
||||
description,
|
||||
verdict: '',
|
||||
title,
|
||||
participants: doc.participants,
|
||||
company,
|
||||
location
|
||||
})
|
||||
|
@ -32,7 +32,8 @@
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', {
|
||||
ignoreKeys: ['number', 'comments', 'title', 'description', 'verdict']
|
||||
ignoreKeys: ['number', 'comments', 'title', 'description', 'verdict'],
|
||||
ignoreMixins: [calendar.mixin.Reminder]
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -73,6 +73,7 @@ export interface Applicant extends Task {
|
||||
*/
|
||||
export interface Review extends Event {
|
||||
attachedTo: Ref<Candidate>
|
||||
number: number
|
||||
|
||||
verdict: string
|
||||
|
||||
|
@ -82,19 +82,18 @@
|
||||
$: if (object && prevSelected !== object._class) {
|
||||
prevSelected = object._class
|
||||
selectedClass = Hierarchy.mixinOrClass(object)
|
||||
|
||||
|
||||
parentClass = getParentClass(object._class)
|
||||
mixins = getMixins()
|
||||
getMixins()
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getMixins (): Mixin<Doc>[] {
|
||||
const descendants = hierarchy.getDescendants(parentClass)
|
||||
const mixins = descendants.filter(
|
||||
(m) => hierarchy.getClass(m).kind === ClassifierKind.MIXIN && hierarchy.hasMixin(object, m)
|
||||
function getMixins (): void {
|
||||
const descendants = hierarchy.getDescendants(parentClass).map((p) => hierarchy.getClass(p))
|
||||
mixins = descendants.filter(
|
||||
(m) => m.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(object, m._id) && !ignoreMixins.has(m._id)
|
||||
)
|
||||
return mixins.map((m) => hierarchy.getClass(m) as Mixin<Doc>)
|
||||
}
|
||||
|
||||
function filterKeys (keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
|
||||
@ -112,6 +111,7 @@
|
||||
}
|
||||
|
||||
let ignoreKeys: string[] = []
|
||||
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
||||
|
||||
async function updateKeys (): Promise<void> {
|
||||
const filtredKeys = getFiltredKeys(
|
||||
@ -160,6 +160,7 @@
|
||||
}
|
||||
mainEditor = editor
|
||||
updateKeys()
|
||||
getMixins()
|
||||
}
|
||||
|
||||
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
|
||||
@ -263,7 +264,9 @@
|
||||
props={{ object }}
|
||||
on:open={(ev) => {
|
||||
ignoreKeys = ev.detail.ignoreKeys
|
||||
ignoreMixins = new Set(ev.detail.ignoreMixins)
|
||||
updateKeys()
|
||||
getMixins()
|
||||
}}
|
||||
on:click={(ev) => {
|
||||
fullSize = true
|
||||
|
@ -186,7 +186,7 @@ function filterActions (
|
||||
continue
|
||||
}
|
||||
if (target.query !== undefined) {
|
||||
const r = matchQuery([doc], target.query)
|
||||
const r = matchQuery([doc], target.query, doc._class, hierarchy)
|
||||
if (r.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@anticrm/presentation": "~0.6.2",
|
||||
"@anticrm/login": "~0.6.1",
|
||||
"@anticrm/setting": "~0.6.0",
|
||||
"@anticrm/calendar": "~0.6.0",
|
||||
"@anticrm/notification": "~0.6.0",
|
||||
"@anticrm/notification-resources": "~0.6.0",
|
||||
"@anticrm/contact": "~0.6.5",
|
||||
|
@ -33,6 +33,7 @@
|
||||
} from '@anticrm/ui'
|
||||
import type { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@anticrm/workbench'
|
||||
import { onDestroy } from 'svelte'
|
||||
import calendar from '@anticrm/calendar'
|
||||
import workbench from '../plugin'
|
||||
import AccountPopup from './AccountPopup.svelte'
|
||||
import ActivityStatus from './ActivityStatus.svelte'
|
||||
@ -252,6 +253,15 @@
|
||||
}}
|
||||
/>
|
||||
<div class="flex-row" style="margin-bottom: 2rem;">
|
||||
<AppItem
|
||||
icon={calendar.icon.Reminder}
|
||||
label={calendar.string.Reminders}
|
||||
selected={false}
|
||||
action={async () => {
|
||||
showPopup(calendar.component.RemindersPopup, {}, 'account')
|
||||
}}
|
||||
notify={false}
|
||||
/>
|
||||
<AppItem
|
||||
icon={notification.icon.Notifications}
|
||||
label={notification.string.Notifications}
|
||||
|
Loading…
Reference in New Issue
Block a user