mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-27 21:43:20 +03:00
parent
4577878d0c
commit
1db5547318
@ -25,6 +25,7 @@ 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'
|
||||
|
||||
export * from '@anticrm/calendar'
|
||||
|
||||
@ -35,6 +36,7 @@ export const DOMAIN_CALENDAR = 'calendar' as Domain
|
||||
export class TCalendar extends TSpaceWithStates implements Calendar {}
|
||||
|
||||
@Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_CALENDAR)
|
||||
@UX(calendar.string.Event, calendar.icon.Calendar)
|
||||
export class TEvent extends TAttachedDoc implements Event {
|
||||
@Prop(TypeString(), calendar.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
@ -85,6 +87,22 @@ export function createModel (builder: Builder): void {
|
||||
]
|
||||
}
|
||||
}, calendar.app.Calendar)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
label: calendar.string.Calendar,
|
||||
icon: calendar.icon.Calendar,
|
||||
component: calendar.component.CalendarView
|
||||
},
|
||||
calendar.viewlet.Calendar
|
||||
)
|
||||
|
||||
// Use generic child presenter
|
||||
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: view.component.ObjectPresenter
|
||||
})
|
||||
}
|
||||
|
||||
export default calendar
|
||||
|
@ -15,17 +15,22 @@
|
||||
|
||||
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 { mergeIds } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { ViewletDescriptor } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(calendarId, calendar, {
|
||||
component: {
|
||||
CreateCalendar: '' as AnyComponent
|
||||
CreateCalendar: '' as AnyComponent,
|
||||
CalendarView: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
ApplicationLabelCalendar: '' as IntlString
|
||||
ApplicationLabelCalendar: '' as IntlString,
|
||||
Event: '' as IntlString
|
||||
},
|
||||
space: {
|
||||
viewlet: {
|
||||
Calendar: '' as Ref<ViewletDescriptor>
|
||||
}
|
||||
})
|
||||
|
@ -81,6 +81,20 @@ export function createReviewModel (builder: Builder): void {
|
||||
archived: false
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: recruit.class.Review,
|
||||
descriptor: calendar.viewlet.Calendar,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
options: {
|
||||
lookup: {
|
||||
attachedTo: recruit.mixin.Candidate,
|
||||
participants: contact.class.Employee,
|
||||
company: contact.class.Organization
|
||||
}
|
||||
} as FindOptions<Doc>,
|
||||
config: []
|
||||
})
|
||||
}
|
||||
|
||||
function createTableViewlet (builder: Builder): void {
|
||||
|
@ -16,6 +16,8 @@
|
||||
"NotSelected": "Not selected",
|
||||
"Today": "Today",
|
||||
"English": "English",
|
||||
"Russian": "Russian"
|
||||
"Russian": "Russian",
|
||||
"CalendarLeft": "<",
|
||||
"CalendarRight": ">"
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
"NotSelected": "Не выбрано",
|
||||
"Today": "Сегодня",
|
||||
"English": "Английский",
|
||||
"Russian": "Русский"
|
||||
"Russian": "Русский",
|
||||
"CalendarLeft": "<",
|
||||
"CalendarRight": ">"
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
import Icon from './Icon.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let label: IntlString
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let primary: boolean = false
|
||||
export let size: 'small' | 'medium' = 'medium'
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
@ -49,7 +49,9 @@
|
||||
{#if loading}
|
||||
<Spinner />
|
||||
{:else}
|
||||
<Label {label} />
|
||||
{#if label}
|
||||
<Label {label} />
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
|
111
packages/ui/src/components/calendar/MonthCalendar.svelte
Normal file
111
packages/ui/src/components/calendar/MonthCalendar.svelte
Normal file
@ -0,0 +1,111 @@
|
||||
<!--
|
||||
// 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 type="ts">
|
||||
import { areDatesEqual, day, firstDay, getWeekDayName, isWeekend, weekday } from './internal/DateUtils'
|
||||
|
||||
export let mondayStart = true
|
||||
export let weekFormat: 'narrow' | 'short' | 'long' | undefined = 'short'
|
||||
export let cellHeight: string | undefined = undefined
|
||||
export let value: Date = new Date()
|
||||
export let currentDate: Date = new Date()
|
||||
export let displayedWeeksCount = 6
|
||||
|
||||
$: firstDayOfCurrentMonth = firstDay(currentDate, mondayStart)
|
||||
|
||||
function onSelect (date: Date) {
|
||||
value = date
|
||||
}
|
||||
|
||||
const todayDate = new Date()
|
||||
</script>
|
||||
|
||||
<div class="month-calendar">
|
||||
<div class="days-of-week-header">
|
||||
{#each [...Array(7).keys()] as dayOfWeek}
|
||||
<div class="day-name">{getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), weekFormat)}</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="days-of-month">
|
||||
{#each [...Array(displayedWeeksCount).keys()] as weekIndex}
|
||||
{#each [...Array(7).keys()] as dayOfWeek}
|
||||
<div style={`grid-column-start: ${dayOfWeek + 1}; grid-row-start: ${weekIndex + 1}`}>
|
||||
<div style={`display: flex; width: 100%; height: ${cellHeight ? `${cellHeight};` : '100%;'}`}>
|
||||
<div
|
||||
class="cell flex-center"
|
||||
class:weekend={isWeekend(weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek))}
|
||||
class:today={areDatesEqual(todayDate, weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek))}
|
||||
class:selected={weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek).getMonth() ===
|
||||
currentDate.getMonth() && areDatesEqual(value, weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek))}
|
||||
class:wrongMonth={weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek).getMonth() !==
|
||||
currentDate.getMonth()}
|
||||
on:click={() => onSelect(weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek))}
|
||||
>
|
||||
{#if !$$slots.cell || weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek).getMonth() !== currentDate.getMonth()}
|
||||
{weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek).getDate()}
|
||||
{:else}
|
||||
<slot name="cell" date={weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek)} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.day-name,
|
||||
.selected-month-controller {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.days-of-week-header,
|
||||
.days-of-month {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
}
|
||||
.weekend {
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
}
|
||||
.today {
|
||||
color: #a66600;
|
||||
}
|
||||
.selected {
|
||||
border-radius: 3px;
|
||||
background-color: var(--primary-button-enabled);
|
||||
border-color: var(--primary-button-focused-border);
|
||||
color: var(--primary-button-color);
|
||||
}
|
||||
.cell {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.cell:hover:not(.wrongMonth) {
|
||||
border: 1px solid var(--primary-button-focused-border);
|
||||
background-color: var(--primary-button-enabled);
|
||||
color: var(--primary-button-color);
|
||||
}
|
||||
.wrongMonth {
|
||||
color: var(--grayscale-grey-03);
|
||||
}
|
||||
.month-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0 5px;
|
||||
color: var(--theme-content-dark-color);
|
||||
}
|
||||
</style>
|
68
packages/ui/src/components/calendar/YearCalendar.svelte
Normal file
68
packages/ui/src/components/calendar/YearCalendar.svelte
Normal file
@ -0,0 +1,68 @@
|
||||
<!--
|
||||
// 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 type="ts">
|
||||
import MonthCalendar from './MonthCalendar.svelte'
|
||||
|
||||
/**
|
||||
* If passed, calendars will use monday as first day
|
||||
*/
|
||||
export let mondayStart = true
|
||||
export let value: Date = new Date()
|
||||
export let currentDate: Date = new Date()
|
||||
export let cellHeight: string | undefined = undefined
|
||||
export let minWidth = '18rem'
|
||||
|
||||
function getMonthName (date: Date): string {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, { month: 'long' }).format(date)
|
||||
}
|
||||
function month (date: Date, m: number): Date {
|
||||
date = new Date(date)
|
||||
date.setMonth(m)
|
||||
return date
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="year-erp-calendar">
|
||||
{#each [...Array(12).keys()] as m}
|
||||
<div class="antiComponentBox mt-2 mb-2 ml-2 mr-2 flex-grow" style={`min-width: ${minWidth};`}>
|
||||
{getMonthName(month(value, m))}
|
||||
<MonthCalendar {cellHeight} weekFormat="narrow" bind:value currentDate={month(currentDate, m)} {mondayStart}>
|
||||
<!----> eslint-disable-next-line no-undef -->
|
||||
<svelte:fragment slot="cell" let:date={date}>
|
||||
<slot name="cell" date={date} />
|
||||
</svelte:fragment>
|
||||
</MonthCalendar>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.year-erp-calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border-collapse: collapse;
|
||||
.row {
|
||||
display: table-row;
|
||||
}
|
||||
.th {
|
||||
display: table-cell;
|
||||
}
|
||||
.calendar {
|
||||
display: table-cell;
|
||||
padding: 0.3em;
|
||||
}
|
||||
}
|
||||
</style>
|
69
packages/ui/src/components/calendar/internal/DateUtils.ts
Normal file
69
packages/ui/src/components/calendar/internal/DateUtils.ts
Normal file
@ -0,0 +1,69 @@
|
||||
// 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.
|
||||
|
||||
|
||||
const DAYS_IN_WEEK = 7
|
||||
const MILLISECONDS_IN_DAY = 86400000
|
||||
|
||||
export function firstDay (date: Date, mondayStart: boolean): Date {
|
||||
const firstDayOfMonth = new Date(date)
|
||||
firstDayOfMonth.setDate(1) // First day of month
|
||||
const result = new Date(firstDayOfMonth)
|
||||
result.setDate(
|
||||
result.getDate() - result.getDay() + (mondayStart ? 1 : 0)
|
||||
)
|
||||
// Check if we need add one more week
|
||||
if (result.getTime() > firstDayOfMonth.getTime()) {
|
||||
result.setDate(result.getDate() - DAYS_IN_WEEK)
|
||||
}
|
||||
result.setHours(0)
|
||||
result.setMinutes(0)
|
||||
result.setSeconds(0)
|
||||
result.setMilliseconds(0)
|
||||
return result
|
||||
}
|
||||
|
||||
export function getWeekDayName (weekDay: Date, weekFormat: 'narrow' | 'short' | 'long' | undefined = 'short'): string {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
weekday: weekFormat
|
||||
}).format(weekDay)
|
||||
}
|
||||
|
||||
export function day (firstDay: Date, offset: number): Date {
|
||||
return new Date(firstDay.getTime() + offset * MILLISECONDS_IN_DAY)
|
||||
}
|
||||
|
||||
export function weekday (firstDay: Date, w: number, d: number): Date {
|
||||
return day(firstDay, w * DAYS_IN_WEEK + d)
|
||||
}
|
||||
|
||||
export function areDatesEqual (firstDate: Date | undefined, secondDate: Date | undefined): boolean {
|
||||
if (firstDate === undefined || secondDate === undefined) {
|
||||
return false
|
||||
}
|
||||
return (
|
||||
firstDate.getFullYear() === secondDate.getFullYear() &&
|
||||
firstDate.getMonth() === secondDate.getMonth() &&
|
||||
firstDate.getDate() === secondDate.getDate()
|
||||
)
|
||||
}
|
||||
|
||||
export function isWeekend (date: Date): boolean {
|
||||
return date.getDay() === 0 || date.getDay() === 6
|
||||
}
|
||||
|
||||
export function getMonthName (date: Date): string {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, { month: 'long' }).format(date)
|
||||
}
|
@ -116,3 +116,6 @@ addStringsLoader(uiId, async (lang: string) => {
|
||||
|
||||
export { default } from './plugin'
|
||||
export * from './colors'
|
||||
|
||||
export { default as MonthCalendar } from './components/calendar/MonthCalendar.svelte'
|
||||
export { default as YearCalendar } from './components/calendar/YearCalendar.svelte'
|
||||
|
@ -181,11 +181,11 @@
|
||||
{/if}
|
||||
{#if isMessageType(m.attribute)}
|
||||
<div class="strong message emphasized">
|
||||
<svelte:component this={m.presenter} {value} />
|
||||
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="strong">
|
||||
<svelte:component this={m.presenter} {value} />
|
||||
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@ -206,11 +206,11 @@
|
||||
</span>
|
||||
{#if isMessageType(m.attribute)}
|
||||
<div class="strong message emphasized">
|
||||
<svelte:component this={m.presenter} {value} />
|
||||
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="strong">
|
||||
<svelte:component this={m.presenter} {value} />
|
||||
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -12,6 +12,14 @@
|
||||
"Title": "Title",
|
||||
"Location": "Location",
|
||||
"Company": "Company",
|
||||
"CreateCalendar": "Create Calendar"
|
||||
"CreateCalendar": "Create Calendar",
|
||||
"Calendar": "Calendar",
|
||||
"Events": "Events",
|
||||
"Event": "Event",
|
||||
"ModeDay": "Day",
|
||||
"ModeWeek": "Week",
|
||||
"ModeMonth": "Month",
|
||||
"ModeYear": "Year",
|
||||
"Today": "Today"
|
||||
}
|
||||
}
|
@ -12,6 +12,14 @@
|
||||
"Title": "Название",
|
||||
"Location": "Местоположение",
|
||||
"Company": "Компания",
|
||||
"CreateCalendar": "Новый Калеедарь"
|
||||
"CreateCalendar": "Новый Календарь",
|
||||
"Calendar": "Календарь",
|
||||
"Events": "События",
|
||||
"Event": "Событие",
|
||||
"ModeDay": "День",
|
||||
"ModeWeek": "Неделя",
|
||||
"ModeMonth": "Месяц",
|
||||
"ModeYear": "Год",
|
||||
"Today": "Сегодня"
|
||||
}
|
||||
}
|
228
plugins/calendar-resources/src/components/CalendarView.svelte
Normal file
228
plugins/calendar-resources/src/components/CalendarView.svelte
Normal file
@ -0,0 +1,228 @@
|
||||
<!--
|
||||
// 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, Doc, DocumentQuery, FindOptions, Ref, SortingOrder, Space } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Button, IconBack, IconForward, MonthCalendar, ScrollBox, YearCalendar } from '@anticrm/ui'
|
||||
import calendar from '../plugin'
|
||||
import Day from './Day.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let query: DocumentQuery<Event> = {}
|
||||
export let options: FindOptions<Event> | undefined = undefined
|
||||
export let baseMenuClass: Ref<Class<Event>> | undefined = undefined
|
||||
export let config: string[]
|
||||
export let search: string = ''
|
||||
|
||||
const mondayStart = true
|
||||
|
||||
// Current selected day
|
||||
let value: Date = new Date()
|
||||
|
||||
const sortKey = 'modifiedOn'
|
||||
const sortOrder = SortingOrder.Descending
|
||||
|
||||
let loading = false
|
||||
let resultQuery: DocumentQuery<Event>
|
||||
$: resultQuery = search === '' ? { ...query, space } : { ...query, $search: search, space }
|
||||
|
||||
let objects: Event[] = []
|
||||
|
||||
const q = createQuery()
|
||||
|
||||
async function update (
|
||||
_class: Ref<Class<Event>>,
|
||||
query: DocumentQuery<Event>,
|
||||
sortKey: string,
|
||||
sortOrder: SortingOrder,
|
||||
options?: FindOptions<Event>
|
||||
) {
|
||||
loading = true
|
||||
q.query<Event>(
|
||||
_class,
|
||||
query,
|
||||
(result) => {
|
||||
objects = result
|
||||
loading = false
|
||||
},
|
||||
{ sort: { [sortKey]: sortOrder }, ...options }
|
||||
)
|
||||
}
|
||||
$: update(_class, resultQuery, sortKey, sortOrder, options)
|
||||
|
||||
function areDatesLess (firstDate: Date, secondDate: Date): boolean {
|
||||
return (
|
||||
firstDate.getFullYear() <= secondDate.getFullYear() &&
|
||||
firstDate.getMonth() <= secondDate.getMonth() &&
|
||||
firstDate.getDate() <= secondDate.getDate()
|
||||
)
|
||||
}
|
||||
|
||||
function findEvents (events: Event[], date: Date): Event[] {
|
||||
return events.filter((it) => areDatesLess(new Date(it.date), date) && areDatesLess(date, new Date(it.dueDate ?? it.date)))
|
||||
}
|
||||
|
||||
interface ShiftType {
|
||||
yearShift: number
|
||||
monthShift: number
|
||||
dayShift: number
|
||||
weekShift:number
|
||||
}
|
||||
|
||||
let shifts: ShiftType = {
|
||||
yearShift: 0,
|
||||
monthShift: 0,
|
||||
dayShift: 0,
|
||||
weekShift: 0
|
||||
}
|
||||
let date = new Date()
|
||||
function inc (val: number): void {
|
||||
if (val === 0) {
|
||||
shifts = {
|
||||
yearShift: 0,
|
||||
monthShift: 0,
|
||||
dayShift: 0,
|
||||
weekShift: 0
|
||||
}
|
||||
return
|
||||
}
|
||||
switch (mode) {
|
||||
case CalendarMode.Day: {
|
||||
shifts = { ...shifts, dayShift: val === 0 ? 0 : shifts.dayShift + val }
|
||||
break
|
||||
}
|
||||
case CalendarMode.Week: {
|
||||
shifts = { ...shifts, weekShift: val === 0 ? 0 : shifts.weekShift + val }
|
||||
break
|
||||
}
|
||||
case CalendarMode.Month: {
|
||||
shifts = { ...shifts, monthShift: val === 0 ? 0 : shifts.monthShift + val }
|
||||
break
|
||||
}
|
||||
case CalendarMode.Year: {
|
||||
shifts = { ...shifts, yearShift: val === 0 ? 0 : shifts.yearShift + val }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
function getMonthName (date: Date): string {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
month: 'long'
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
function currentDate (date: Date, shifts: ShiftType): Date {
|
||||
const res = new Date(date)
|
||||
res.setMonth(date.getMonth() + shifts.monthShift)
|
||||
res.setFullYear(date.getFullYear() + shifts.yearShift)
|
||||
res.setDate(date.getDate() + shifts.dayShift + shifts.weekShift * 7)
|
||||
return res
|
||||
}
|
||||
|
||||
enum CalendarMode { Day, Week, Month, Year }
|
||||
|
||||
let mode: CalendarMode = CalendarMode.Year
|
||||
|
||||
function label (date: Date, mode: CalendarMode): string {
|
||||
switch (mode) {
|
||||
case CalendarMode.Day: {
|
||||
return `${date.getDate()} ${getMonthName(date)} ${date.getFullYear()}`
|
||||
}
|
||||
case CalendarMode.Week: {
|
||||
return `${getMonthName(date)} ${date.getFullYear()}`
|
||||
}
|
||||
case CalendarMode.Month: {
|
||||
return `${getMonthName(date)} ${date.getFullYear()}`
|
||||
}
|
||||
case CalendarMode.Year: {
|
||||
return `${date.getFullYear()}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='fs-title ml-2 mb-2 flex-row-center'>
|
||||
{label(currentDate(date, shifts), mode)}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mb-4">
|
||||
<!-- <Button
|
||||
size={'small'}
|
||||
label={calendar.string.ModeDay}
|
||||
on:click={() => {
|
||||
mode = CalendarMode.Day
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size={'small'}
|
||||
label={calendar.string.ModeWeek}
|
||||
on:click={() => {
|
||||
mode = CalendarMode.Week
|
||||
}}
|
||||
/> -->
|
||||
<Button
|
||||
size={'small'}
|
||||
label={calendar.string.ModeMonth}
|
||||
on:click={() => {
|
||||
date = value
|
||||
shifts = {
|
||||
dayShift: 0,
|
||||
monthShift: 0,
|
||||
weekShift: 0,
|
||||
yearShift: 0
|
||||
}
|
||||
mode = CalendarMode.Month
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size={'small'}
|
||||
label={calendar.string.ModeYear}
|
||||
on:click={() => {
|
||||
date = value
|
||||
shifts = {
|
||||
dayShift: 0,
|
||||
monthShift: 0,
|
||||
weekShift: 0,
|
||||
yearShift: 0
|
||||
}
|
||||
mode = CalendarMode.Year
|
||||
}}
|
||||
/>
|
||||
<div class="flex ml-4 gap-1">
|
||||
<Button icon={IconBack} size={'small'} on:click={() => { inc(-1) } }/>
|
||||
<Button size={'small'} label={calendar.string.Today} on:click={() => { inc(0) }}/>
|
||||
<Button icon={IconForward} size={'small'} on:click={() => { inc(1) }}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<ScrollBox bothScroll>
|
||||
{#if mode === CalendarMode.Year}
|
||||
<YearCalendar {mondayStart} cellHeight={'2.5rem'} bind:value={value} currentDate={currentDate(date, shifts)}>
|
||||
<svelte:fragment slot="cell" let:date>
|
||||
<Day events={findEvents(objects, date)} {date} />
|
||||
</svelte:fragment>
|
||||
</YearCalendar>
|
||||
{:else if mode === CalendarMode.Month}
|
||||
<MonthCalendar {mondayStart} cellHeight={'5rem'} bind:value={value} currentDate={currentDate(date, shifts)}>
|
||||
<svelte:fragment slot="cell" let:date={date}>
|
||||
<Day events={findEvents(objects, date)} {date} size={'huge'}/>
|
||||
</svelte:fragment>
|
||||
</MonthCalendar>
|
||||
{/if}
|
||||
</ScrollBox>
|
54
plugins/calendar-resources/src/components/Day.svelte
Normal file
54
plugins/calendar-resources/src/components/Day.svelte
Normal file
@ -0,0 +1,54 @@
|
||||
<!--
|
||||
// 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 { Tooltip } from '@anticrm/ui'
|
||||
import calendar from '../plugin'
|
||||
import EventsPopup from './EventsPopup.svelte'
|
||||
|
||||
export let events: Event[]
|
||||
export let date: Date
|
||||
export let size: 'small' | 'huge' = 'small'
|
||||
</script>
|
||||
|
||||
{#if events.length > 0}
|
||||
<Tooltip label={calendar.string.Events} component={EventsPopup} props={{ value: events }}>
|
||||
<div class="cell" class:huge={size === 'huge'}>
|
||||
{date.getDate()}
|
||||
<div class="marker" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{:else}
|
||||
{date.getDate()}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.marker {
|
||||
// position: relative;
|
||||
top: -0.25rem;
|
||||
width: 0.25rem;
|
||||
height: 0.25rem;
|
||||
border-radius: 50%;
|
||||
background-color: var(--highlight-red);
|
||||
}
|
||||
.huge {
|
||||
width: 5rem;
|
||||
}
|
||||
.cell {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
38
plugins/calendar-resources/src/components/EventsPopup.svelte
Normal file
38
plugins/calendar-resources/src/components/EventsPopup.svelte
Normal file
@ -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 type { Event } from '@anticrm/calendar'
|
||||
import core from '@anticrm/core'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import calendar from '../plugin'
|
||||
|
||||
export let value: Event[]
|
||||
</script>
|
||||
|
||||
<Table
|
||||
_class={calendar.class.Event}
|
||||
config={['', 'title', '$lookup.space.name', 'date', 'dueDate', 'modifiedOn']}
|
||||
options={
|
||||
{
|
||||
lookup: {
|
||||
space: core.class.Space
|
||||
}
|
||||
}
|
||||
}
|
||||
query={ { _id: { $in: value.map(it => it._id) } } }
|
||||
loadingProps={{ length: value.length ?? 0 }}
|
||||
/>
|
@ -16,9 +16,11 @@
|
||||
import { Resources } from '@anticrm/platform'
|
||||
|
||||
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
||||
import CalendarView from './components/CalendarView.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
PersonsPresenter
|
||||
PersonsPresenter,
|
||||
CalendarView
|
||||
}
|
||||
})
|
||||
|
@ -14,11 +14,17 @@
|
||||
//
|
||||
|
||||
import calendar, { calendarId } from '@anticrm/calendar'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import { IntlString, mergeIds } from '@anticrm/platform'
|
||||
|
||||
export default mergeIds(calendarId, calendar, {
|
||||
component: {
|
||||
},
|
||||
string: {
|
||||
Events: '' as IntlString,
|
||||
ModeDay: '' as IntlString,
|
||||
ModeWeek: '' as IntlString,
|
||||
ModeMonth: '' as IntlString,
|
||||
ModeYear: '' as IntlString,
|
||||
Today: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -6,6 +6,7 @@
|
||||
"declaration": true,
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
|
Loading…
Reference in New Issue
Block a user