mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-28 22:13:29 +03:00
parent
4577878d0c
commit
1db5547318
@ -25,6 +25,7 @@ import core, { TAttachedDoc } from '@anticrm/model-core'
|
|||||||
import { TSpaceWithStates } from '@anticrm/model-task'
|
import { TSpaceWithStates } from '@anticrm/model-task'
|
||||||
import workbench from '@anticrm/model-workbench'
|
import workbench from '@anticrm/model-workbench'
|
||||||
import calendar from './plugin'
|
import calendar from './plugin'
|
||||||
|
import view from '@anticrm/model-view'
|
||||||
|
|
||||||
export * from '@anticrm/calendar'
|
export * from '@anticrm/calendar'
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ export const DOMAIN_CALENDAR = 'calendar' as Domain
|
|||||||
export class TCalendar extends TSpaceWithStates implements Calendar {}
|
export class TCalendar extends TSpaceWithStates implements Calendar {}
|
||||||
|
|
||||||
@Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_CALENDAR)
|
@Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_CALENDAR)
|
||||||
|
@UX(calendar.string.Event, calendar.icon.Calendar)
|
||||||
export class TEvent extends TAttachedDoc implements Event {
|
export class TEvent extends TAttachedDoc implements Event {
|
||||||
@Prop(TypeString(), calendar.string.Title)
|
@Prop(TypeString(), calendar.string.Title)
|
||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
@ -85,6 +87,22 @@ export function createModel (builder: Builder): void {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}, calendar.app.Calendar)
|
}, 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
|
export default calendar
|
||||||
|
@ -15,17 +15,22 @@
|
|||||||
|
|
||||||
import { calendarId } from '@anticrm/calendar'
|
import { calendarId } from '@anticrm/calendar'
|
||||||
import calendar from '@anticrm/calendar-resources/src/plugin'
|
import calendar from '@anticrm/calendar-resources/src/plugin'
|
||||||
|
import { Ref } from '@anticrm/core'
|
||||||
import type { IntlString } from '@anticrm/platform'
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import { mergeIds } from '@anticrm/platform'
|
import { mergeIds } from '@anticrm/platform'
|
||||||
import { AnyComponent } from '@anticrm/ui'
|
import { AnyComponent } from '@anticrm/ui'
|
||||||
|
import { ViewletDescriptor } from '@anticrm/view'
|
||||||
|
|
||||||
export default mergeIds(calendarId, calendar, {
|
export default mergeIds(calendarId, calendar, {
|
||||||
component: {
|
component: {
|
||||||
CreateCalendar: '' as AnyComponent
|
CreateCalendar: '' as AnyComponent,
|
||||||
|
CalendarView: '' as AnyComponent
|
||||||
},
|
},
|
||||||
string: {
|
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
|
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 {
|
function createTableViewlet (builder: Builder): void {
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
"NotSelected": "Not selected",
|
"NotSelected": "Not selected",
|
||||||
"Today": "Today",
|
"Today": "Today",
|
||||||
"English": "English",
|
"English": "English",
|
||||||
"Russian": "Russian"
|
"Russian": "Russian",
|
||||||
|
"CalendarLeft": "<",
|
||||||
|
"CalendarRight": ">"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
"NotSelected": "Не выбрано",
|
"NotSelected": "Не выбрано",
|
||||||
"Today": "Сегодня",
|
"Today": "Сегодня",
|
||||||
"English": "Английский",
|
"English": "Английский",
|
||||||
"Russian": "Русский"
|
"Russian": "Русский",
|
||||||
|
"CalendarLeft": "<",
|
||||||
|
"CalendarRight": ">"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
import Icon from './Icon.svelte'
|
import Icon from './Icon.svelte'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
export let label: IntlString
|
export let label: IntlString | undefined = undefined
|
||||||
export let primary: boolean = false
|
export let primary: boolean = false
|
||||||
export let size: 'small' | 'medium' = 'medium'
|
export let size: 'small' | 'medium' = 'medium'
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
@ -49,8 +49,10 @@
|
|||||||
{#if loading}
|
{#if loading}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else}
|
{:else}
|
||||||
|
{#if label}
|
||||||
<Label {label} />
|
<Label {label} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
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 { default } from './plugin'
|
||||||
export * from './colors'
|
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}
|
||||||
{#if isMessageType(m.attribute)}
|
{#if isMessageType(m.attribute)}
|
||||||
<div class="strong message emphasized">
|
<div class="strong message emphasized">
|
||||||
<svelte:component this={m.presenter} {value} />
|
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="strong">
|
<div class="strong">
|
||||||
<svelte:component this={m.presenter} {value} />
|
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
@ -206,11 +206,11 @@
|
|||||||
</span>
|
</span>
|
||||||
{#if isMessageType(m.attribute)}
|
{#if isMessageType(m.attribute)}
|
||||||
<div class="strong message emphasized">
|
<div class="strong message emphasized">
|
||||||
<svelte:component this={m.presenter} {value} />
|
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="strong">
|
<div class="strong">
|
||||||
<svelte:component this={m.presenter} {value} />
|
<svelte:component this={m.presenter} {value} attributeType={m.attribute?.type} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -12,6 +12,14 @@
|
|||||||
"Title": "Title",
|
"Title": "Title",
|
||||||
"Location": "Location",
|
"Location": "Location",
|
||||||
"Company": "Company",
|
"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": "Название",
|
"Title": "Название",
|
||||||
"Location": "Местоположение",
|
"Location": "Местоположение",
|
||||||
"Company": "Компания",
|
"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 { Resources } from '@anticrm/platform'
|
||||||
|
|
||||||
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
||||||
|
import CalendarView from './components/CalendarView.svelte'
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
component: {
|
component: {
|
||||||
PersonsPresenter
|
PersonsPresenter,
|
||||||
|
CalendarView
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -14,11 +14,17 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import calendar, { calendarId } from '@anticrm/calendar'
|
import calendar, { calendarId } from '@anticrm/calendar'
|
||||||
import { mergeIds } from '@anticrm/platform'
|
import { IntlString, mergeIds } from '@anticrm/platform'
|
||||||
|
|
||||||
export default mergeIds(calendarId, calendar, {
|
export default mergeIds(calendarId, calendar, {
|
||||||
component: {
|
component: {
|
||||||
},
|
},
|
||||||
string: {
|
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,
|
"declaration": true,
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"esnext",
|
"esnext",
|
||||||
|
Loading…
Reference in New Issue
Block a user