mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-23 14:06:00 +03:00
Updated Calendar layout. (#3504)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
ba6a3e8638
commit
c7efa0d113
@ -183,6 +183,7 @@
|
||||
--theme-toggle-bg-hover: rgba(120, 120, 128, 0.64);
|
||||
--theme-toggle-on-bg-color: #205dc2;
|
||||
--theme-toggle-on-bg-hover: #1A53AF;
|
||||
--theme-radio-bg-color: #343442;
|
||||
|
||||
--theme-error-color: #eb5757;
|
||||
--theme-urgent-color: #F5694A;
|
||||
@ -371,6 +372,7 @@
|
||||
--theme-toggle-bg-hover: rgba(120, 120, 128, 0.64);
|
||||
--theme-toggle-on-bg-color: #205dc2;
|
||||
--theme-toggle-on-bg-hover: #1A53AF;
|
||||
--theme-radio-bg-color: #E5E5E5;
|
||||
|
||||
--theme-error-color: #eb5757; // Dark
|
||||
--theme-urgent-color: #F5694A;
|
||||
|
@ -902,6 +902,7 @@ a.no-line {
|
||||
.background-button-noborder-bg-hover { background-color: var(--noborder-bg-hover); }
|
||||
.background-primary-color { background-color: var(--accented-button-default); }
|
||||
.background-content-accent-color { background-color: var(--accent-color); }
|
||||
.background-comp-header-color { background-color: var(--theme-comp-header-color); }
|
||||
|
||||
.content-trans-color { color: var(--theme-trans-color); }
|
||||
.content-darker-color { color: var(--theme-darker-color); }
|
||||
|
@ -87,7 +87,7 @@
|
||||
on:click={openPopup}
|
||||
>
|
||||
<span slot="content" class="overflow-label disabled flex-grow text-left mr-2">
|
||||
<Label label={selectedItem ? selectedItem.label : label} {params} />
|
||||
<Label label={selectedItem ? selectedItem.label : label} params={selectedItem ? selectedItem.params : params} />
|
||||
</span>
|
||||
<svelte:fragment slot="iconRight">
|
||||
<DropdownIcon size={'small'} fill={'var(--theme-dark-color)'} />
|
||||
|
431
packages/ui/src/components/calendar/DayCalendar.svelte
Normal file
431
packages/ui/src/components/calendar/DayCalendar.svelte
Normal file
@ -0,0 +1,431 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Timestamp } from '@hcengineering/core'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { resizeObserver, deviceOptionsStore as deviceInfo, Scroller } from '../..'
|
||||
import {
|
||||
MILLISECONDS_IN_DAY,
|
||||
addZero,
|
||||
day as getDay,
|
||||
getMonday,
|
||||
getWeekDayName,
|
||||
areDatesEqual
|
||||
} from './internal/DateUtils'
|
||||
import { CalendarItem } from '../../types'
|
||||
|
||||
export let events: CalendarItem[]
|
||||
export let mondayStart = true
|
||||
export let selectedDate: Date = new Date()
|
||||
export let currentDate: Date = selectedDate
|
||||
export let displayedDaysCount = 7
|
||||
export let displayedHours = 24
|
||||
export let startFromWeekStart = true
|
||||
export let weekFormat: 'narrow' | 'short' | 'long' | undefined = displayedDaysCount > 4 ? 'short' : 'long'
|
||||
// export let startHour = 0
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const todayDate = new Date()
|
||||
|
||||
$: fontSize = $deviceInfo.fontSize
|
||||
$: weekMonday = startFromWeekStart
|
||||
? getMonday(currentDate, mondayStart)
|
||||
: new Date(new Date(currentDate).setHours(0, 0, 0, 0))
|
||||
|
||||
interface CalendarCell {
|
||||
id: number
|
||||
start: number
|
||||
end: number
|
||||
day: number
|
||||
element?: HTMLDivElement
|
||||
}
|
||||
interface CalendarElement {
|
||||
id: string
|
||||
date: Timestamp
|
||||
dueDate: Timestamp
|
||||
cols: number
|
||||
}
|
||||
interface CalendarRow {
|
||||
elements: CalendarElement[]
|
||||
}
|
||||
interface CalendarGrid {
|
||||
columns: CalendarRow[]
|
||||
}
|
||||
|
||||
const cells: CalendarCell[] = Array<CalendarCell>(displayedHours * 2 * displayedDaysCount)
|
||||
let container: HTMLElement
|
||||
let scroller: HTMLElement
|
||||
let calendarWidth: number = 0
|
||||
let calendarRect: DOMRect
|
||||
let colWidth: number = 0
|
||||
|
||||
for (let hourOfDay = 0; hourOfDay < displayedHours; hourOfDay++) {
|
||||
for (let line = 1; line >= 0; line--) {
|
||||
for (let dayOfWeek = 0; dayOfWeek < displayedDaysCount; dayOfWeek++) {
|
||||
const cell = line
|
||||
? hourOfDay * 2 + displayedHours * 2 * dayOfWeek
|
||||
: hourOfDay * 2 + displayedHours * 2 * dayOfWeek + 1
|
||||
cells[cell] = {
|
||||
id: cell,
|
||||
start: line ? hourOfDay * 100 : hourOfDay * 100 + 30,
|
||||
end: line ? hourOfDay * 100 + 30 : (hourOfDay + 1) * 100,
|
||||
day: dayOfWeek
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let newEvents = events
|
||||
let grid: CalendarGrid[] = Array<CalendarGrid>(displayedDaysCount)
|
||||
$: if (newEvents !== events) {
|
||||
grid = new Array<CalendarGrid>(displayedDaysCount)
|
||||
newEvents = events
|
||||
}
|
||||
$: newEvents
|
||||
.filter((ev) => !ev.allDay)
|
||||
.forEach((event, i, arr) => {
|
||||
if (grid[event.day] === undefined) {
|
||||
grid[event.day] = {
|
||||
columns: [{ elements: [{ id: event.eventId, date: event.date, dueDate: event.dueDate, cols: 1 }] }]
|
||||
}
|
||||
} else {
|
||||
const index = grid[event.day].columns.findIndex(
|
||||
(col) => col.elements[col.elements.length - 1].dueDate <= event.date
|
||||
)
|
||||
if (index === -1) {
|
||||
const intersects = grid[event.day].columns.filter((col) =>
|
||||
checkIntersect(col.elements[col.elements.length - 1], event)
|
||||
)
|
||||
const size = intersects.length + 1
|
||||
grid[event.day].columns.forEach((col) => {
|
||||
if (checkIntersect(col.elements[col.elements.length - 1], event)) {
|
||||
col.elements[col.elements.length - 1].cols = size
|
||||
}
|
||||
})
|
||||
grid[event.day].columns.push({
|
||||
elements: [{ id: event.eventId, date: event.date, dueDate: event.dueDate, cols: size }]
|
||||
})
|
||||
} else {
|
||||
const intersects = grid[event.day].columns.filter((col) =>
|
||||
checkIntersect(col.elements[col.elements.length - 1], event)
|
||||
)
|
||||
let maxCols = 1
|
||||
intersects.forEach((col) => {
|
||||
if (col.elements[col.elements.length - 1].cols > maxCols) {
|
||||
maxCols = col.elements[col.elements.length - 1].cols
|
||||
}
|
||||
})
|
||||
if (intersects.length >= maxCols) maxCols = intersects.length + 1
|
||||
grid[event.day].columns.forEach((col) => {
|
||||
if (checkIntersect(col.elements[col.elements.length - 1], event)) {
|
||||
col.elements[col.elements.length - 1].cols = maxCols
|
||||
}
|
||||
})
|
||||
grid[event.day].columns[index].elements.push({
|
||||
id: event.eventId,
|
||||
date: event.date,
|
||||
dueDate: event.dueDate,
|
||||
cols: maxCols
|
||||
})
|
||||
}
|
||||
}
|
||||
if (i === arr.length - 1) checkGrid()
|
||||
})
|
||||
const checkGrid = () => {
|
||||
for (let i = 0; i < displayedDaysCount; i++) {
|
||||
if (grid[i] === null || typeof grid[i] !== 'object' || grid[i].columns.length === 0) continue
|
||||
for (let j = 0; j < grid[i].columns.length - 1; j++) {
|
||||
grid[i].columns[j].elements.forEach((el) => {
|
||||
grid[i].columns[j + 1].elements.forEach((nextEl) => {
|
||||
if (checkIntersect(el, nextEl)) {
|
||||
if (el.cols > nextEl.cols) nextEl.cols = el.cols
|
||||
else el.cols = nextEl.cols
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const checkSizes = (element: HTMLElement | Element) => {
|
||||
calendarRect = element.getBoundingClientRect()
|
||||
calendarWidth = calendarRect.width
|
||||
colWidth = (calendarWidth - 3.5 * fontSize) / displayedDaysCount
|
||||
}
|
||||
const getCellByTime = (time: number, day: number, end: boolean = false): CalendarCell | undefined => {
|
||||
return end
|
||||
? cells.filter((cell) => cell.start < time && time <= cell.end && cell.day === day)[0]
|
||||
: cells.filter((cell) => cell.start <= time && time < cell.end && cell.day === day)[0]
|
||||
}
|
||||
const calcTimeOffset = (
|
||||
date: Timestamp | Date,
|
||||
rect: DOMRect,
|
||||
needCorrect: boolean,
|
||||
end: boolean = false
|
||||
): number => {
|
||||
const mins = new Date(date).getMinutes()
|
||||
if (mins === 0 || mins === 30) return end ? 2 : 1
|
||||
else if (mins < 30) {
|
||||
const res = end ? ((30 - mins) / 30) * rect.height : (mins / 30) * rect.height
|
||||
return needCorrect ? res + 1 : res
|
||||
} else {
|
||||
const res = end ? ((60 - mins) / 30) * rect.height : ((mins - 30) / 30) * rect.height
|
||||
return needCorrect ? res + 1 : res
|
||||
}
|
||||
}
|
||||
const checkIntersect = (date1: CalendarItem | CalendarElement, date2: CalendarItem | CalendarElement): boolean => {
|
||||
return (
|
||||
(date2.date <= date1.date && date2.dueDate > date1.date) ||
|
||||
(date2.date >= date1.date && date2.date < date1.dueDate)
|
||||
)
|
||||
}
|
||||
const convertToTime = (date: Timestamp | Date): number => {
|
||||
const temp = new Date(date)
|
||||
return temp.getHours() * 100 + temp.getMinutes()
|
||||
}
|
||||
|
||||
const getRect = (event: CalendarItem): { top: number; bottom: number; left: number; right: number } => {
|
||||
const result = { top: 0, bottom: 0, left: 0, right: 0, width: 0 }
|
||||
const checkDate = new Date(currentDate.getTime() + MILLISECONDS_IN_DAY * event.day)
|
||||
const startDay = checkDate.setHours(0, 0, 0)
|
||||
const endDay = checkDate.setHours(displayedHours - 1, 59, 59)
|
||||
const startTime = event.date < startDay ? 0 : convertToTime(event.date)
|
||||
const endTime = event.dueDate > endDay ? displayedHours * 100 : convertToTime(event.dueDate)
|
||||
const startCell = getCellByTime(startTime, event.day)
|
||||
const endCell = getCellByTime(endTime, event.day, true)
|
||||
|
||||
const scrollOffset = scroller?.scrollTop ?? 0
|
||||
if (startCell?.element && endCell?.element) {
|
||||
const rectStart = startCell.element.getBoundingClientRect()
|
||||
const rectEnd = startCell.id === endCell.id ? rectStart : endCell.element.getBoundingClientRect()
|
||||
const startTimeOffset = calcTimeOffset(event.date, rectStart, startCell.id !== endCell.id)
|
||||
const endTimeOffset = calcTimeOffset(event.dueDate, rectEnd, startCell.id !== endCell.id, true)
|
||||
result.top = rectStart.top - calendarRect.top + startTimeOffset + scrollOffset
|
||||
result.bottom = calendarRect.bottom - rectEnd.bottom + endTimeOffset - scrollOffset
|
||||
|
||||
let cols = 1
|
||||
let index: number = 0
|
||||
grid[event.day].columns.forEach((col, i) =>
|
||||
col.elements.forEach((el) => {
|
||||
if (el.id === event.eventId) {
|
||||
cols = el.cols
|
||||
index = i
|
||||
}
|
||||
})
|
||||
)
|
||||
const elWidth = (rectStart.width - 0.25 * fontSize - (cols - 1) * 0.125 * fontSize) / cols
|
||||
result.width = elWidth
|
||||
result.left = rectStart.left - calendarRect.left + 0.125 * fontSize + index * elWidth + index * 0.125 * fontSize
|
||||
result.right =
|
||||
calendarRect.right -
|
||||
rectEnd.right +
|
||||
0.125 * fontSize +
|
||||
(cols - index - 1) * elWidth +
|
||||
(cols - index - 1) * 0.125 * fontSize
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (container) checkSizes(container)
|
||||
})
|
||||
</script>
|
||||
|
||||
<Scroller bind:divScroll={scroller} fade={{ multipler: { top: 5.75, bottom: 0 } }}>
|
||||
<div
|
||||
bind:this={container}
|
||||
class="calendar-container"
|
||||
style:grid={`[header] 3.5rem [all-day] 2.25rem repeat(${
|
||||
displayedHours * 2
|
||||
}, [row-start] 2rem) / [time-col] 3.5rem repeat(${displayedDaysCount}, [col-start] 1fr)`}
|
||||
use:resizeObserver={(element) => checkSizes(element)}
|
||||
>
|
||||
<div class="sticky-header head center"><span class="zone">CEST</span></div>
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayOfWeek}
|
||||
{@const day = getDay(weekMonday, dayOfWeek)}
|
||||
<div class="sticky-header head title" class:center={displayedDaysCount > 1}>
|
||||
<span class="day" class:today={areDatesEqual(todayDate, day)}>{day.getDate()}</span>
|
||||
<span class="weekday">{getWeekDayName(day, weekFormat)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="sticky-header center text-sm content-dark-color">All day</div>
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayOfWeek}
|
||||
{@const day = getDay(weekMonday, dayOfWeek)}
|
||||
{@const alldays = events.filter((ev) => ev.allDay && ev.day === dayOfWeek)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="sticky-header"
|
||||
class:allday-container={alldays.length > 0}
|
||||
style:width={`${colWidth}px`}
|
||||
style:grid-template-columns={`repeat(${alldays.length}, minmax(0, 1fr))`}
|
||||
on:click|stopPropagation={() =>
|
||||
dispatch('create', { day, hour: -1, halfHour: false, date: new Date(day.setHours(0, 0, 0, 0)) })}
|
||||
>
|
||||
{#each alldays as ad}
|
||||
<div class="allday-event">
|
||||
<slot name="allday" id={ad.eventId} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#each [...Array(displayedHours).keys()] as hourOfDay}
|
||||
{#each [...Array(2).keys()] as half}
|
||||
{#if hourOfDay === 0 && half === 0}
|
||||
<div class="clear-cell" />
|
||||
{:else if hourOfDay < displayedHours - 1 && half}
|
||||
<div class="time-cell" style:grid-row={`row-start ${hourOfDay * 2 + 2} / row-start ${hourOfDay * 2 + 4}`}>
|
||||
{addZero(hourOfDay + 1)}:00
|
||||
</div>
|
||||
{:else if half}
|
||||
<div class="clear-cell" />
|
||||
{/if}
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayOfWeek}
|
||||
{@const day = getDay(weekMonday, dayOfWeek)}
|
||||
{@const cell = hourOfDay * 2 + displayedHours * 2 * dayOfWeek + half}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
bind:this={cells[cell].element}
|
||||
class="empty-cell {half === 0 ? 'first-half' : 'second-half'}"
|
||||
style:grid-column={`col-start ${dayOfWeek + 1} / ${dayOfWeek + 2}`}
|
||||
on:click|stopPropagation={() => {
|
||||
dispatch('create', {
|
||||
day,
|
||||
hour: hourOfDay,
|
||||
halfHour: half === 1,
|
||||
date: new Date(day.setHours(hourOfDay, half * 30, 0, 0))
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/each}
|
||||
{/each}
|
||||
|
||||
{#key calendarWidth}
|
||||
{#each events.filter((ev) => !ev.allDay) as event, i}
|
||||
{@const rect = getRect(event)}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
class="calendar-element"
|
||||
style:top={`${rect.top}px`}
|
||||
style:bottom={`${rect.bottom}px`}
|
||||
style:left={`${rect.left}px`}
|
||||
style:right={`${rect.right}px`}
|
||||
tabindex={1000 + i}
|
||||
>
|
||||
<slot name="event" id={event.eventId} />
|
||||
</div>
|
||||
{/each}
|
||||
{/key}
|
||||
</div>
|
||||
</Scroller>
|
||||
|
||||
<style lang="scss">
|
||||
.first-half,
|
||||
.second-half {
|
||||
border-left: 1px solid var(--theme-divider-color);
|
||||
}
|
||||
.second-half {
|
||||
border-bottom: 1px solid var(--theme-divider-color);
|
||||
}
|
||||
.empty-cell {
|
||||
}
|
||||
.clear-cell {
|
||||
}
|
||||
.time-cell {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--theme-dark-color);
|
||||
}
|
||||
.calendar-element {
|
||||
position: absolute;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.sticky-header {
|
||||
position: sticky;
|
||||
background-color: var(--theme-comp-header-color);
|
||||
z-index: 10;
|
||||
|
||||
&.head {
|
||||
top: 0;
|
||||
}
|
||||
&:not(.head) {
|
||||
top: 3.5rem;
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
border-bottom: 1px solid var(--theme-divider-color);
|
||||
}
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
&.title {
|
||||
font-size: 1.125rem;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
&:not(.center) {
|
||||
padding-left: 1.375rem;
|
||||
}
|
||||
|
||||
.day.today {
|
||||
padding: 0.375rem;
|
||||
color: var(--accented-button-color);
|
||||
background-color: #3871e0;
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
.weekday {
|
||||
margin-left: 0.25rem;
|
||||
opacity: 0.4;
|
||||
|
||||
&::first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(.allday-container) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&.allday-container {
|
||||
display: inline-grid;
|
||||
justify-items: stretch;
|
||||
gap: 0.125rem;
|
||||
padding: 0.125rem;
|
||||
max-width: 100%;
|
||||
|
||||
.allday-event {
|
||||
background-color: red;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.zone {
|
||||
padding: 0.375rem;
|
||||
font-size: 0.625rem;
|
||||
color: var(--theme-dark-color);
|
||||
background-color: rgba(64, 109, 223, 0.1);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
.calendar-container {
|
||||
will-change: transform;
|
||||
position: relative;
|
||||
display: grid;
|
||||
}
|
||||
</style>
|
@ -15,6 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { areDatesEqual, day, firstDay, getWeekDayName, isWeekend, weekday } from './internal/DateUtils'
|
||||
import { Scroller, defaultSP } from '../..'
|
||||
|
||||
export let mondayStart = true
|
||||
export let weekFormat: 'narrow' | 'short' | 'long' | undefined = 'short'
|
||||
@ -34,43 +35,45 @@
|
||||
const todayDate = new Date()
|
||||
</script>
|
||||
|
||||
<div class="month-calendar flex-grow">
|
||||
<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}
|
||||
<Scroller fade={defaultSP}>
|
||||
<div class="month-calendar flex-grow">
|
||||
<div class="days-of-week-header">
|
||||
{#each [...Array(7).keys()] as dayOfWeek}
|
||||
{@const date = weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek)}
|
||||
<div style={`grid-column-start: ${dayOfWeek + 1}; grid-row-start: ${weekIndex + 1}`}>
|
||||
<div style={`display: flex; width: 100%; height: ${cellHeight ? `${cellHeight};` : '100%;'}`}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="cell flex-center"
|
||||
class:weekend={isWeekend(date)}
|
||||
class:wrongMonth={date.getMonth() !== currentDate.getMonth()}
|
||||
on:click={() => onSelect(date)}
|
||||
>
|
||||
{#if !$$slots.cell}
|
||||
{date.getDate()}
|
||||
{:else}
|
||||
<slot
|
||||
name="cell"
|
||||
{date}
|
||||
today={areDatesEqual(todayDate, date)}
|
||||
selected={areDatesEqual(selectedDate, date)}
|
||||
wrongMonth={date.getMonth() !== currentDate.getMonth()}
|
||||
/>
|
||||
{/if}
|
||||
<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}
|
||||
{@const date = weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek)}
|
||||
<div style={`grid-column-start: ${dayOfWeek + 1}; grid-row-start: ${weekIndex + 1}`}>
|
||||
<div style={`display: flex; width: 100%; height: ${cellHeight ? `${cellHeight};` : '100%;'}`}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="cell flex-center"
|
||||
class:weekend={isWeekend(date)}
|
||||
class:wrongMonth={date.getMonth() !== currentDate.getMonth()}
|
||||
on:click={() => onSelect(date)}
|
||||
>
|
||||
{#if !$$slots.cell}
|
||||
{date.getDate()}
|
||||
{:else}
|
||||
<slot
|
||||
name="cell"
|
||||
{date}
|
||||
today={areDatesEqual(todayDate, date)}
|
||||
selected={areDatesEqual(selectedDate, date)}
|
||||
wrongMonth={date.getMonth() !== currentDate.getMonth()}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Scroller>
|
||||
|
||||
<style lang="scss">
|
||||
.month-calendar {
|
||||
|
@ -14,8 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ui from '../../plugin'
|
||||
import Label from '../Label.svelte'
|
||||
import ui, { Scroller, Label } from '../..'
|
||||
import { addZero, areDatesEqual, day as getDay, getMonday, getWeekDayName } from './internal/DateUtils'
|
||||
|
||||
export let mondayStart = true
|
||||
@ -36,47 +35,49 @@
|
||||
: new Date(new Date(currentDate).setHours(0, 0, 0, 0))
|
||||
</script>
|
||||
|
||||
<table>
|
||||
<thead class="scroller-thead">
|
||||
<tr class="scroller-thead__tr">
|
||||
<th><Label label={ui.string.HoursLabel} /></th>
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayOfWeek}
|
||||
{@const day = getDay(weekMonday, dayOfWeek)}
|
||||
<th>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="cursor-pointer uppercase flex-col-center"
|
||||
class:today={areDatesEqual(todayDate, day)}
|
||||
on:click={() => {
|
||||
dispatch('select', day)
|
||||
}}
|
||||
>
|
||||
<div class="flex-center">{getWeekDayName(day, 'short')}</div>
|
||||
<div class="flex-center">{day.getDate()}</div>
|
||||
</div>
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each [...Array(displayedHours).keys()] as hourOfDay}
|
||||
<tr>
|
||||
<td style="width: 50px;" class="calendar-td first">
|
||||
{#if hourOfDay !== 0}
|
||||
{addZero(hourOfDay)}:00
|
||||
{/if}
|
||||
</td>
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayIndex}
|
||||
<td class="calendar-td cell" style={`height: ${cellHeight};`}>
|
||||
{#if $$slots.cell}
|
||||
<slot name="cell" date={getDay(weekMonday, dayIndex, hourOfDay * 60)} />
|
||||
{/if}
|
||||
</td>
|
||||
<Scroller fade={{ multipler: { top: 3, bottom: 0 } }}>
|
||||
<table>
|
||||
<thead class="scroller-thead">
|
||||
<tr class="scroller-thead__tr">
|
||||
<th><Label label={ui.string.HoursLabel} /></th>
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayOfWeek}
|
||||
{@const day = getDay(weekMonday, dayOfWeek)}
|
||||
<th>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="cursor-pointer uppercase flex-col-center"
|
||||
class:today={areDatesEqual(todayDate, day)}
|
||||
on:click={() => {
|
||||
dispatch('select', day)
|
||||
}}
|
||||
>
|
||||
<div class="flex-center">{getWeekDayName(day, 'short')}</div>
|
||||
<div class="flex-center">{day.getDate()}</div>
|
||||
</div>
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each [...Array(displayedHours).keys()] as hourOfDay}
|
||||
<tr>
|
||||
<td style="width: 50px;" class="calendar-td first">
|
||||
{#if hourOfDay !== 0}
|
||||
{addZero(hourOfDay)}:00
|
||||
{/if}
|
||||
</td>
|
||||
{#each [...Array(displayedDaysCount).keys()] as dayIndex}
|
||||
<td class="calendar-td cell" style:height={cellHeight}>
|
||||
{#if $$slots.cell}
|
||||
<slot name="cell" date={getDay(weekMonday, dayIndex, hourOfDay * 60)} />
|
||||
{/if}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</Scroller>
|
||||
|
||||
<style lang="scss">
|
||||
table {
|
||||
|
@ -14,6 +14,8 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import MonthCalendar from './MonthCalendar.svelte'
|
||||
import Scroller from '../Scroller.svelte'
|
||||
import { defaultSP } from '../..'
|
||||
|
||||
/**
|
||||
* If passed, calendars will use monday as first day
|
||||
@ -35,25 +37,27 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="year-erp-calendar">
|
||||
{#each [...Array(12).keys()] as m}
|
||||
<div class="antiComponentBox flex-col flex-grow flex-wrap" style={`min-width: ${minWidth};`}>
|
||||
<span class="month-caption">{getMonthName(month(currentDate, m))}</span>
|
||||
<MonthCalendar
|
||||
{cellHeight}
|
||||
weekFormat="narrow"
|
||||
bind:selectedDate
|
||||
currentDate={month(currentDate, m)}
|
||||
{mondayStart}
|
||||
on:change
|
||||
>
|
||||
<svelte:fragment slot="cell" let:date let:today let:selected let:wrongMonth>
|
||||
<slot name="cell" {date} {today} {selected} {wrongMonth} />
|
||||
</svelte:fragment>
|
||||
</MonthCalendar>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<Scroller padding={'0 2.25rem'} fade={defaultSP}>
|
||||
<div class="year-erp-calendar">
|
||||
{#each [...Array(12).keys()] as m}
|
||||
<div class="antiComponentBox flex-col flex-grow flex-wrap" style={`min-width: ${minWidth};`}>
|
||||
<span class="month-caption">{getMonthName(month(currentDate, m))}</span>
|
||||
<MonthCalendar
|
||||
{cellHeight}
|
||||
weekFormat="narrow"
|
||||
bind:selectedDate
|
||||
currentDate={month(currentDate, m)}
|
||||
{mondayStart}
|
||||
on:change
|
||||
>
|
||||
<svelte:fragment slot="cell" let:date let:today let:selected let:wrongMonth>
|
||||
<slot name="cell" {date} {today} {selected} {wrongMonth} />
|
||||
</svelte:fragment>
|
||||
</MonthCalendar>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Scroller>
|
||||
|
||||
<style lang="scss">
|
||||
.year-erp-calendar {
|
||||
|
@ -3,6 +3,6 @@
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="8,11.7 1.6,5.4 2.4,4.6 8,10.3 13.6,4.6 14.4,5.4 " />
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 22L6 12L7.4 10.6L16 19.2L24.6 10.6L26 12L16 22Z" />
|
||||
</svg>
|
||||
|
@ -168,6 +168,7 @@ export { default as Panel } from './components/Panel.svelte'
|
||||
export { default as MonthCalendar } from './components/calendar/MonthCalendar.svelte'
|
||||
export { default as YearCalendar } from './components/calendar/YearCalendar.svelte'
|
||||
export { default as WeekCalendar } from './components/calendar/WeekCalendar.svelte'
|
||||
export { default as DayCalendar } from './components/calendar/DayCalendar.svelte'
|
||||
|
||||
export { default as FocusHandler } from './components/FocusHandler.svelte'
|
||||
export { default as ListView } from './components/ListView.svelte'
|
||||
|
@ -379,3 +379,15 @@ export interface DialogStep {
|
||||
readonly component: AnyComponent | AnySvelteComponent
|
||||
props?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CalendarItem {
|
||||
eventId: string
|
||||
allDay: boolean
|
||||
date: Timestamp
|
||||
dueDate: Timestamp
|
||||
day: number
|
||||
access: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
|
||||
}
|
||||
|
@ -32,19 +32,23 @@
|
||||
IconBack,
|
||||
IconForward,
|
||||
MonthCalendar,
|
||||
Scroller,
|
||||
CalendarItem,
|
||||
DayCalendar,
|
||||
WeekCalendar,
|
||||
YearCalendar,
|
||||
areDatesEqual,
|
||||
defaultSP,
|
||||
getMonday,
|
||||
showPopup
|
||||
DropdownLabelsIntl,
|
||||
showPopup,
|
||||
MILLISECONDS_IN_DAY
|
||||
} from '@hcengineering/ui'
|
||||
import { BuildModelKey } from '@hcengineering/view'
|
||||
import { CalendarMode } from '../index'
|
||||
import calendar from '../plugin'
|
||||
import Day from './Day.svelte'
|
||||
import Hour from './Hour.svelte'
|
||||
import EventElement from './EventElement.svelte'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
@ -64,6 +68,9 @@
|
||||
|
||||
function getFrom (date: Date, mode: CalendarMode): Timestamp {
|
||||
switch (mode) {
|
||||
case CalendarMode.Days: {
|
||||
return new Date(date).setHours(0, 0, 0, 0)
|
||||
}
|
||||
case CalendarMode.Day: {
|
||||
return new Date(date).setHours(0, 0, 0, 0)
|
||||
}
|
||||
@ -81,6 +88,9 @@
|
||||
|
||||
function getTo (date: Date, mode: CalendarMode): Timestamp {
|
||||
switch (mode) {
|
||||
case CalendarMode.Days: {
|
||||
return new Date(date).setDate(date.getDate() + 1)
|
||||
}
|
||||
case CalendarMode.Day: {
|
||||
return new Date(date).setDate(date.getDate() + 1)
|
||||
}
|
||||
@ -163,6 +173,10 @@
|
||||
return
|
||||
}
|
||||
switch (mode) {
|
||||
case CalendarMode.Days: {
|
||||
currentDate.setDate(currentDate.getDate() + val * 3)
|
||||
break
|
||||
}
|
||||
case CalendarMode.Day: {
|
||||
currentDate.setDate(currentDate.getDate() + val)
|
||||
break
|
||||
@ -187,31 +201,8 @@
|
||||
month: 'long'
|
||||
}).format(date)
|
||||
}
|
||||
function getWeekName (date: Date): string {
|
||||
const onejan = new Date(date.getFullYear(), 0, 1)
|
||||
const week = Math.ceil(((date.getTime() - onejan.getTime()) / 86400000 + onejan.getDay() + 1) / 7)
|
||||
|
||||
return `W${week}`
|
||||
}
|
||||
|
||||
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 `${getWeekName(date)} ${getMonthName(date)} ${date.getFullYear()}`
|
||||
}
|
||||
case CalendarMode.Month: {
|
||||
return `${getMonthName(date)} ${date.getFullYear()}`
|
||||
}
|
||||
case CalendarMode.Year: {
|
||||
return `${date.getFullYear()}`
|
||||
}
|
||||
}
|
||||
}
|
||||
let mode: CalendarMode = CalendarMode.Days
|
||||
|
||||
function showCreateDialog (date: Date, withTime: boolean) {
|
||||
if (createComponent === undefined) {
|
||||
@ -221,13 +212,68 @@
|
||||
}
|
||||
|
||||
let indexes = new Map<Ref<Event>, number>()
|
||||
|
||||
const ddItems: {
|
||||
id: string | number
|
||||
label: IntlString
|
||||
mode: CalendarMode
|
||||
params?: Record<string, any>
|
||||
}[] = [
|
||||
{ id: 'day', label: calendar.string.ModeDay, mode: CalendarMode.Day },
|
||||
{ id: 'days', label: calendar.string.DueDays, mode: CalendarMode.Days, params: { days: 3 } },
|
||||
{ id: 'week', label: calendar.string.ModeWeek, mode: CalendarMode.Week },
|
||||
{ id: 'month', label: calendar.string.ModeMonth, mode: CalendarMode.Month },
|
||||
{ id: 'year', label: calendar.string.ModeYear, mode: CalendarMode.Year }
|
||||
]
|
||||
|
||||
const toCalendar = (events: Event[], date: Date, days: number = 1): CalendarItem[] => {
|
||||
const result: CalendarItem[] = []
|
||||
for (let day = 0; day < days; day++) {
|
||||
const startDate = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(0, 0, 0)
|
||||
const lastDate = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(23, 59, 59)
|
||||
events.forEach((event) => {
|
||||
const eventStart = event.allDay
|
||||
? new Date(event.date + new Date().getTimezoneOffset() * 60 * 1000).getTime()
|
||||
: event.date
|
||||
const eventEnd = event.allDay
|
||||
? new Date(event.dueDate + new Date().getTimezoneOffset() * 60 * 1000).getTime()
|
||||
: event.dueDate
|
||||
if ((eventStart <= startDate && eventEnd > startDate) || (eventStart >= startDate && eventStart < lastDate)) {
|
||||
result.push({
|
||||
eventId: event.eventId,
|
||||
allDay: event.allDay,
|
||||
date: event.date,
|
||||
dueDate: event.dueDate,
|
||||
day,
|
||||
access: event.access
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="text-lg fs-bold px-10 my-4 flex-no-shrink clear-mins">
|
||||
{label(currentDate, mode)}
|
||||
</div>
|
||||
<div class="flex-between mb-4 px-10 flex-no-shrink clear-mins">
|
||||
<div class="calendar-header">
|
||||
<div class="title">
|
||||
{getMonthName(currentDate)}
|
||||
<span>{currentDate.getFullYear()}</span>
|
||||
</div>
|
||||
<div class="flex-row-center gap-2">
|
||||
<DropdownLabelsIntl
|
||||
items={ddItems.map((it) => {
|
||||
return { id: it.id, label: it.label, params: it.params }
|
||||
})}
|
||||
size={'medium'}
|
||||
selected={ddItems.find((it) => it.mode === mode)?.id}
|
||||
on:selected={(e) => (mode = ddItems.find((it) => it.id === e.detail)?.mode ?? ddItems[0].mode)}
|
||||
/>
|
||||
<Button
|
||||
label={calendar.string.Today}
|
||||
on:click={() => {
|
||||
inc(0)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconBack}
|
||||
kind={'ghost'}
|
||||
@ -235,13 +281,6 @@
|
||||
inc(-1)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label={calendar.string.Today}
|
||||
kind={'ghost'}
|
||||
on:click={() => {
|
||||
inc(0)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconForward}
|
||||
kind={'ghost'}
|
||||
@ -250,139 +289,176 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-center gap-2 clear-mins">
|
||||
<Button
|
||||
label={calendar.string.ModeDay}
|
||||
on:click={() => {
|
||||
mode = CalendarMode.Day
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label={calendar.string.ModeWeek}
|
||||
on:click={() => {
|
||||
mode = CalendarMode.Week
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label={calendar.string.ModeMonth}
|
||||
on:click={() => {
|
||||
mode = CalendarMode.Month
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
label={calendar.string.ModeYear}
|
||||
on:click={() => {
|
||||
mode = CalendarMode.Year
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Scroller
|
||||
padding={'0 2.25rem'}
|
||||
fade={mode === CalendarMode.Week || mode === CalendarMode.Day ? { multipler: { top: 3, bottom: 0 } } : defaultSP}
|
||||
>
|
||||
{#if mode === CalendarMode.Year}
|
||||
<YearCalendar
|
||||
{mondayStart}
|
||||
cellHeight={'2.5rem'}
|
||||
bind:selectedDate
|
||||
bind:currentDate
|
||||
on:change={(e) => {
|
||||
currentDate = e.detail
|
||||
if (areDatesEqual(selectedDate, currentDate)) {
|
||||
mode = CalendarMode.Month
|
||||
}
|
||||
selectedDate = e.detail
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="cell" let:date let:today let:selected let:wrongMonth>
|
||||
<Day
|
||||
events={findEvents(objects, date)}
|
||||
{date}
|
||||
{_class}
|
||||
{baseMenuClass}
|
||||
{options}
|
||||
{config}
|
||||
{today}
|
||||
{selected}
|
||||
{wrongMonth}
|
||||
{query}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</YearCalendar>
|
||||
{:else if mode === CalendarMode.Month}
|
||||
<MonthCalendar {mondayStart} cellHeight={'8.5rem'} bind:selectedDate bind:currentDate>
|
||||
<svelte:fragment slot="cell" let:date let:today let:selected let:wrongMonth>
|
||||
<Day
|
||||
events={findEvents(objects, date)}
|
||||
{date}
|
||||
size={'huge'}
|
||||
{_class}
|
||||
{baseMenuClass}
|
||||
{options}
|
||||
{config}
|
||||
{today}
|
||||
{selected}
|
||||
{wrongMonth}
|
||||
{query}
|
||||
on:select={(e) => {
|
||||
currentDate = e.detail
|
||||
if (areDatesEqual(selectedDate, currentDate)) {
|
||||
mode = CalendarMode.Day
|
||||
}
|
||||
selectedDate = e.detail
|
||||
}}
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, false)
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</MonthCalendar>
|
||||
{:else if mode === CalendarMode.Week}
|
||||
<WeekCalendar
|
||||
{mondayStart}
|
||||
cellHeight={'4.5rem'}
|
||||
bind:selectedDate
|
||||
bind:currentDate
|
||||
on:select={(e) => {
|
||||
currentDate = e.detail
|
||||
selectedDate = e.detail
|
||||
mode = CalendarMode.Day
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="cell" let:date>
|
||||
<Hour
|
||||
events={findEvents(objects, date, true)}
|
||||
{date}
|
||||
bind:indexes
|
||||
{#if mode === CalendarMode.Year}
|
||||
<YearCalendar
|
||||
{mondayStart}
|
||||
cellHeight={'2.5rem'}
|
||||
bind:selectedDate
|
||||
bind:currentDate
|
||||
on:change={(e) => {
|
||||
currentDate = e.detail
|
||||
if (areDatesEqual(selectedDate, currentDate)) {
|
||||
mode = CalendarMode.Month
|
||||
}
|
||||
selectedDate = e.detail
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="cell" let:date let:today let:selected let:wrongMonth>
|
||||
<Day
|
||||
events={findEvents(objects, date)}
|
||||
{date}
|
||||
{_class}
|
||||
{baseMenuClass}
|
||||
{options}
|
||||
{config}
|
||||
{today}
|
||||
{selected}
|
||||
{wrongMonth}
|
||||
{query}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</YearCalendar>
|
||||
{:else if mode === CalendarMode.Month}
|
||||
<MonthCalendar {mondayStart} cellHeight={'8.5rem'} bind:selectedDate bind:currentDate>
|
||||
<svelte:fragment slot="cell" let:date let:today let:selected let:wrongMonth>
|
||||
<Day
|
||||
events={findEvents(objects, date)}
|
||||
{date}
|
||||
size={'huge'}
|
||||
{_class}
|
||||
{baseMenuClass}
|
||||
{options}
|
||||
{config}
|
||||
{today}
|
||||
{selected}
|
||||
{wrongMonth}
|
||||
{query}
|
||||
on:select={(e) => {
|
||||
currentDate = e.detail
|
||||
if (areDatesEqual(selectedDate, currentDate)) {
|
||||
mode = CalendarMode.Day
|
||||
}
|
||||
selectedDate = e.detail
|
||||
}}
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, false)
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</MonthCalendar>
|
||||
{:else if mode === CalendarMode.Week}
|
||||
<DayCalendar
|
||||
events={toCalendar(objects, currentDate, 7)}
|
||||
{mondayStart}
|
||||
displayedDaysCount={7}
|
||||
startFromWeekStart={false}
|
||||
bind:selectedDate
|
||||
bind:currentDate
|
||||
on:create={(e) => showCreateDialog(e.detail.date, true)}
|
||||
>
|
||||
<svelte:fragment slot="allday" let:id>
|
||||
{@const event = objects.find((event) => event.eventId === id)}
|
||||
{#if event}
|
||||
<EventElement
|
||||
{event}
|
||||
allday
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, true)
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</WeekCalendar>
|
||||
{:else if mode === CalendarMode.Day}
|
||||
<WeekCalendar
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="event" let:id>
|
||||
{@const event = objects.find((event) => event.eventId === id)}
|
||||
{#if event}
|
||||
<EventElement
|
||||
{event}
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, true)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DayCalendar>
|
||||
{:else if mode === CalendarMode.Day || mode === CalendarMode.Days}
|
||||
{#key mode}
|
||||
<DayCalendar
|
||||
events={toCalendar(objects, currentDate, mode === CalendarMode.Days ? 3 : 1)}
|
||||
{mondayStart}
|
||||
displayedDaysCount={1}
|
||||
displayedDaysCount={mode === CalendarMode.Days ? 3 : 1}
|
||||
startFromWeekStart={false}
|
||||
cellHeight={'4.5rem'}
|
||||
bind:selectedDate
|
||||
bind:currentDate
|
||||
on:create={(e) => showCreateDialog(e.detail.date, true)}
|
||||
>
|
||||
<svelte:fragment slot="cell" let:date>
|
||||
<Hour
|
||||
events={findEvents(objects, date, true)}
|
||||
{date}
|
||||
bind:indexes
|
||||
wide
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, true)
|
||||
}}
|
||||
/>
|
||||
<svelte:fragment slot="allday" let:id>
|
||||
{@const event = objects.find((event) => event.eventId === id)}
|
||||
{#if event}
|
||||
<EventElement
|
||||
{event}
|
||||
allday
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, true)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</WeekCalendar>
|
||||
{/if}
|
||||
</Scroller>
|
||||
<div class="min-h-4 max-h-4 h-4 flex-no-shrink" />
|
||||
<svelte:fragment slot="event" let:id>
|
||||
{@const event = objects.find((event) => event.eventId === id)}
|
||||
{#if event}
|
||||
<EventElement
|
||||
{event}
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, true)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DayCalendar>
|
||||
{/key}
|
||||
{:else if mode === CalendarMode.Day}
|
||||
<WeekCalendar
|
||||
{mondayStart}
|
||||
displayedDaysCount={1}
|
||||
startFromWeekStart={false}
|
||||
cellHeight={'4.5rem'}
|
||||
bind:selectedDate
|
||||
bind:currentDate
|
||||
>
|
||||
<svelte:fragment slot="cell" let:date>
|
||||
<Hour
|
||||
events={findEvents(objects, date, true)}
|
||||
{date}
|
||||
bind:indexes
|
||||
wide
|
||||
on:create={(e) => {
|
||||
showCreateDialog(e.detail, true)
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</WeekCalendar>
|
||||
{/if}
|
||||
|
||||
<!-- <div class="min-h-4 max-h-4 h-4 flex-no-shrink" /> -->
|
||||
<style lang="scss">
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1.75rem 0.75rem 2.25rem;
|
||||
|
||||
.title {
|
||||
font-size: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
&::first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
span {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,78 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { MILLISECONDS_IN_MINUTE, addZero, showPanel, tooltip } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import EventPresenter from './EventPresenter.svelte'
|
||||
|
||||
export let event: Event
|
||||
export let allday: boolean = false
|
||||
|
||||
$: startDate = new Date(event.date)
|
||||
$: endDate = new Date(event.dueDate)
|
||||
$: oneRow = event.dueDate - event.date <= MILLISECONDS_IN_MINUTE * 30 || allday
|
||||
$: narrow = event.dueDate - event.date < MILLISECONDS_IN_MINUTE * 25
|
||||
|
||||
const getTime = (date: Date): string => {
|
||||
return `${addZero(date.getHours())}:${addZero(date.getMinutes())}`
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if event}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="event-container"
|
||||
class:oneRow
|
||||
use:tooltip={oneRow ? { component: EventPresenter, props: { value: event } } : {}}
|
||||
on:click|stopPropagation={() => {
|
||||
if (event) showPanel(view.component.EditDoc, event._id, event._class, 'content')
|
||||
}}
|
||||
>
|
||||
{#if !narrow}
|
||||
<b class:overflow-label={oneRow}>{event.title}</b>
|
||||
{/if}
|
||||
{#if !oneRow}
|
||||
<span class="overflow-label text-sm">{getTime(startDate)}-{getTime(endDate)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.event-container {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
font-size: 0.8125rem;
|
||||
background-color: #f3f6fb;
|
||||
border: 1px solid rgba(43, 81, 144, 0.2);
|
||||
border-left: 0.25rem solid #2b5190;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(.oneRow) {
|
||||
padding: 0.25rem 1rem;
|
||||
}
|
||||
&.oneRow {
|
||||
justify-content: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -17,25 +17,9 @@
|
||||
import { Class, DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
Label,
|
||||
Loading,
|
||||
SearchEdit,
|
||||
showPopup,
|
||||
TabList,
|
||||
IconAdd
|
||||
} from '@hcengineering/ui'
|
||||
import { AnyComponent, Button, Component, Label, Loading, showPopup, TabList, IconAdd } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
import {
|
||||
FilterButton,
|
||||
getViewOptions,
|
||||
setActiveViewletId,
|
||||
ViewletSettingButton,
|
||||
viewOptionStore
|
||||
} from '@hcengineering/view-resources'
|
||||
import { getViewOptions, setActiveViewletId, viewOptionStore } from '@hcengineering/view-resources'
|
||||
import calendar from '../plugin'
|
||||
// import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
|
||||
@ -50,7 +34,7 @@
|
||||
export let createLabel: IntlString | undefined = calendar.string.CreateEvent
|
||||
|
||||
const viewletQuery = createQuery()
|
||||
let search = ''
|
||||
const search = ''
|
||||
let resultQuery: DocumentQuery<Event> = {}
|
||||
|
||||
let viewlets: WithLookup<Viewlet>[] = []
|
||||
@ -106,8 +90,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
// $: twoRows = $deviceInfo.twoRows
|
||||
|
||||
$: viewOptions = getViewOptions(selectedViewlet, $viewOptionStore)
|
||||
</script>
|
||||
|
||||
@ -130,35 +112,25 @@
|
||||
<Button icon={IconAdd} label={createLabel} kind={'accented'} on:click={showCreateDialog} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ac-header full divide search-start">
|
||||
<div class="ac-header-full small-gap">
|
||||
<SearchEdit bind:value={search} on:change={() => updateResultQuery(search)} />
|
||||
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
|
||||
<div class="buttons-divider" />
|
||||
<FilterButton {_class} />
|
||||
</div>
|
||||
<div class="ac-header-full medium-gap">
|
||||
<ViewletSettingButton bind:viewOptions viewlet={selectedViewlet} />
|
||||
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
|
||||
</div>
|
||||
</div>
|
||||
{#if selectedViewlet?.$lookup?.descriptor?.component}
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<Component
|
||||
is={selectedViewlet.$lookup?.descriptor?.component}
|
||||
props={{
|
||||
_class,
|
||||
space,
|
||||
options: selectedViewlet.options,
|
||||
config: preference?.config ?? selectedViewlet.config,
|
||||
viewOptions,
|
||||
viewlet: selectedViewlet,
|
||||
query: resultQuery,
|
||||
search,
|
||||
createComponent
|
||||
}}
|
||||
/>
|
||||
<div class="flex-col w-full h-full background-comp-header-color">
|
||||
{#if selectedViewlet?.$lookup?.descriptor?.component}
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<Component
|
||||
is={selectedViewlet.$lookup?.descriptor?.component}
|
||||
props={{
|
||||
_class,
|
||||
space,
|
||||
options: selectedViewlet.options,
|
||||
config: preference?.config ?? selectedViewlet.config,
|
||||
viewOptions,
|
||||
viewlet: selectedViewlet,
|
||||
query: resultQuery,
|
||||
search,
|
||||
createComponent
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -127,6 +127,7 @@ async function removePast (client: TxOperations, object: ReccuringInstance): Pro
|
||||
|
||||
export enum CalendarMode {
|
||||
Day,
|
||||
Days,
|
||||
Week,
|
||||
Month,
|
||||
Year
|
||||
|
Loading…
Reference in New Issue
Block a user