mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-29 03:21:13 +00:00
TSK-1415 unify date selector (#3129)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
1a98b10eee
commit
83fe8f897b
@ -24,7 +24,6 @@
|
||||
export let value: number | null | undefined = null
|
||||
export let withTime: boolean = false
|
||||
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
|
||||
export let labelOver: IntlString | undefined = undefined
|
||||
export let labelNull: IntlString = ui.string.NoDate
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -42,6 +41,6 @@
|
||||
<div class="antiSelect antiWrapper cursor-default">
|
||||
<div class="flex-col">
|
||||
<span class="label mb-1"><Label label={title} /></span>
|
||||
<DatePresenter {value} {mode} {icon} {labelOver} {labelNull} editable on:change={changeValue} />
|
||||
<DatePresenter {value} {mode} {icon} {labelNull} editable on:change={changeValue} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,144 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { afterUpdate } from 'svelte'
|
||||
import type { AnySvelteComponent } from '../../types'
|
||||
import { dpstore } from '../../popups'
|
||||
|
||||
let component: AnySvelteComponent | undefined
|
||||
let anchor: HTMLElement
|
||||
|
||||
let modalHTML: HTMLElement
|
||||
let frendlyFocus: HTMLElement[] | undefined
|
||||
let componentInstance: any
|
||||
|
||||
$: frendlyFocus = $dpstore.frendlyFocus
|
||||
$: if ($dpstore.anchor) {
|
||||
anchor = $dpstore.anchor
|
||||
if (modalHTML) $dpstore.popup = modalHTML
|
||||
}
|
||||
$: component = $dpstore.component
|
||||
$: shift = $dpstore.shift
|
||||
$: mode = $dpstore.mode
|
||||
|
||||
function _update (result: any): void {
|
||||
fitPopup()
|
||||
}
|
||||
|
||||
function _change (result: any): void {
|
||||
if ($dpstore.onChange !== undefined) $dpstore.onChange(result)
|
||||
}
|
||||
|
||||
function _close (result: any): void {
|
||||
if ($dpstore.onClose !== undefined) $dpstore.onClose(result)
|
||||
}
|
||||
|
||||
function escapeClose () {
|
||||
if (componentInstance && componentInstance.canClose) {
|
||||
if (!componentInstance.canClose()) return
|
||||
}
|
||||
_close(null)
|
||||
}
|
||||
|
||||
const fitPopup = (): void => {
|
||||
if (modalHTML && component) {
|
||||
if (componentInstance) {
|
||||
modalHTML.style.left = modalHTML.style.right = modalHTML.style.top = modalHTML.style.bottom = ''
|
||||
modalHTML.style.maxHeight = modalHTML.style.height = ''
|
||||
|
||||
const rect = anchor.getBoundingClientRect()
|
||||
const rectPopup = modalHTML.getBoundingClientRect()
|
||||
let isMiddle = false
|
||||
// Vertical
|
||||
if (rect.bottom + rectPopup.height + 28 <= document.body.clientHeight) {
|
||||
modalHTML.style.top = `calc(${rect.bottom}px + 1px)`
|
||||
} else if (rectPopup.height + 28 < rect.top) {
|
||||
modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + 1px)`
|
||||
} else {
|
||||
modalHTML.style.top = `calc(${rect.top}px - ${rectPopup.height / 2}px)`
|
||||
isMiddle = true
|
||||
}
|
||||
|
||||
// Horizontal
|
||||
if (rect.left + rectPopup.width + 16 > document.body.clientWidth) {
|
||||
modalHTML.style.right = document.body.clientWidth - rect.right + 'px'
|
||||
} else if (rect.left - rectPopup.width < 0) {
|
||||
modalHTML.style.left = rect.left + rectPopup.width + 28 + 'px'
|
||||
} else if (rect.left - rectPopup.width < document.body.clientWidth && isMiddle) {
|
||||
modalHTML.style.left = rect.left - rectPopup.width + 'px'
|
||||
} else {
|
||||
modalHTML.style.left = rect.left + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown (ev: KeyboardEvent) {
|
||||
if (ev.key === 'Escape' && component) {
|
||||
escapeClose()
|
||||
}
|
||||
}
|
||||
afterUpdate(() => fitPopup())
|
||||
$: component && fitPopup()
|
||||
|
||||
const unfocus = (ev: FocusEvent): void => {
|
||||
const target = ev.relatedTarget as HTMLElement
|
||||
let kl: boolean = false
|
||||
frendlyFocus?.forEach((edit) => {
|
||||
if (edit === target) kl = true
|
||||
})
|
||||
if (target === modalHTML) kl = true
|
||||
if (!kl || target === null) _close(null)
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={fitPopup} on:keydown={handleKeydown} />
|
||||
<div
|
||||
class="popup"
|
||||
class:visibility={component !== undefined}
|
||||
bind:this={modalHTML}
|
||||
tabindex="0"
|
||||
on:blur={(ev) => unfocus(ev)}
|
||||
>
|
||||
{#if component}
|
||||
<svelte:component
|
||||
this={component}
|
||||
bind:mode
|
||||
bind:shift
|
||||
bind:this={componentInstance}
|
||||
on:update={(ev) => _update(ev.detail)}
|
||||
on:change={(ev) => _change(ev.detail)}
|
||||
on:close={(ev) => _close(ev.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.popup {
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
max-height: calc(100vh - 2rem);
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
z-index: 11000;
|
||||
&.visibility {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -23,12 +23,15 @@
|
||||
import IconClose from '../icons/Close.svelte'
|
||||
import MonthSquare from './MonthSquare.svelte'
|
||||
import { daysInMonth } from './internal/DateUtils'
|
||||
import Shifts from './Shifts.svelte'
|
||||
import { DateRangeMode } from '@hcengineering/core'
|
||||
|
||||
export let currentDate: Date | null
|
||||
export let withTime: boolean = false
|
||||
export let mondayStart: boolean = true
|
||||
export let label = currentDate != null ? ui.string.EditDueDate : ui.string.AddDueDate
|
||||
export let detail: IntlString | undefined = undefined
|
||||
export let noShift: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -125,18 +128,20 @@
|
||||
return result
|
||||
}
|
||||
|
||||
const saveDate = (): void => {
|
||||
const saveDate = (withTime: boolean = false): void => {
|
||||
if (currentDate) {
|
||||
currentDate.setHours(edits[3].value > 0 ? edits[3].value : 0)
|
||||
currentDate.setMinutes(edits[4].value > 0 ? edits[4].value : 0)
|
||||
if (!withTime) {
|
||||
currentDate.setHours(edits[3].value > 0 ? edits[3].value : 0)
|
||||
currentDate.setMinutes(edits[4].value > 0 ? edits[4].value : 0)
|
||||
}
|
||||
currentDate.setSeconds(0, 0)
|
||||
viewDate = currentDate = currentDate
|
||||
dateToEdits()
|
||||
dispatch('update', currentDate)
|
||||
}
|
||||
}
|
||||
const closeDP = (): void => {
|
||||
if (!isNull()) saveDate()
|
||||
const closeDP = (withTime: boolean = false): void => {
|
||||
if (!isNull()) saveDate(withTime)
|
||||
else {
|
||||
currentDate = null
|
||||
dispatch('update', null)
|
||||
@ -355,9 +360,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<Button kind={'primary'} label={ui.string.Save} size={'x-large'} width={'100%'} on:click={closeDP} />
|
||||
<Button
|
||||
kind={'primary'}
|
||||
label={ui.string.Save}
|
||||
size={'x-large'}
|
||||
width={'100%'}
|
||||
on:click={() => closeDP(withTime)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Shifts
|
||||
{currentDate}
|
||||
on:change={(evt) => {
|
||||
currentDate = evt.detail
|
||||
closeDP(withTime)
|
||||
}}
|
||||
shift={!noShift}
|
||||
mode={withTime ? DateRangeMode.DATETIME : DateRangeMode.DATE}
|
||||
/>
|
||||
|
||||
<style lang="scss">
|
||||
.date-popup-container {
|
||||
|
@ -31,7 +31,6 @@
|
||||
export let mondayStart: boolean = true
|
||||
export let editable: boolean = false
|
||||
export let icon: 'normal' | 'warning' | 'critical' | 'overdue' = 'normal'
|
||||
export let labelOver: IntlString | undefined = undefined // label instead of date
|
||||
export let labelNull: IntlString = ui.string.NoDate
|
||||
export let showIcon = true
|
||||
export let shouldShowLabel: boolean = true
|
||||
@ -93,9 +92,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if value !== null && value !== undefined}
|
||||
{#if shouldShowLabel && labelOver !== undefined}
|
||||
<Label label={labelOver} />
|
||||
{:else if shouldShowLabel}
|
||||
{#if shouldShowLabel}
|
||||
{new Date(value).getDate()}
|
||||
{getMonthName(new Date(value), 'short')}
|
||||
{#if new Date(value).getFullYear() !== today.getFullYear()}
|
||||
|
@ -24,7 +24,6 @@
|
||||
export let value: number | null | undefined = null
|
||||
export let withTime: boolean = false
|
||||
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
|
||||
export let labelOver: IntlString | undefined = undefined
|
||||
export let labelNull: IntlString = ui.string.NoDate
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -40,14 +39,6 @@
|
||||
<div class="antiSelect antiWrapper cursor-default">
|
||||
<div class="flex-col">
|
||||
<span class="label mb-1"><Label label={title} /></span>
|
||||
<DateRangePresenter
|
||||
{value}
|
||||
mode={DateRangeMode.DATETIME}
|
||||
{icon}
|
||||
{labelOver}
|
||||
{labelNull}
|
||||
editable
|
||||
on:change={changeValue}
|
||||
/>
|
||||
<DateRangePresenter {value} mode={DateRangeMode.DATETIME} {icon} {labelNull} editable on:change={changeValue} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,12 +13,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { dpstore } from '../../popups'
|
||||
import Month from './Month.svelte'
|
||||
import Scroller from '../Scroller.svelte'
|
||||
import TimeShiftPresenter from '../TimeShiftPresenter.svelte'
|
||||
import { DateRangeMode } from '@hcengineering/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Month from './Month.svelte'
|
||||
import Shifts from './Shifts.svelte'
|
||||
|
||||
export let direction: 'before' | 'after' = 'after'
|
||||
export let minutes: number[] = [5, 15, 30]
|
||||
@ -27,9 +25,6 @@
|
||||
export let shift: boolean = false
|
||||
export let mode: DateRangeMode = DateRangeMode.DATE
|
||||
|
||||
$: withTime = mode !== DateRangeMode.DATE
|
||||
$: withDate = mode !== DateRangeMode.TIME
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const today = new Date(Date.now())
|
||||
@ -45,24 +40,8 @@
|
||||
today.getMinutes()
|
||||
)
|
||||
: today
|
||||
$: currentDate = $dpstore.currentDate ?? defaultDate
|
||||
$: currentDate = defaultDate
|
||||
const mondayStart: boolean = true
|
||||
|
||||
$: base = direction === 'before' ? -1 : 1
|
||||
const MINUTE = 60 * 1000
|
||||
const HOUR = 60 * MINUTE
|
||||
const DAY = 24 * HOUR
|
||||
|
||||
const shiftValues: (number | string)[] = []
|
||||
|
||||
$: {
|
||||
if (withTime) {
|
||||
shiftValues.push(...minutes.map((m) => m * MINUTE), 'divider', ...hours.map((m) => m * HOUR))
|
||||
}
|
||||
if (withDate) {
|
||||
shiftValues.push('divider', ...days.map((m) => m * DAY))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="month-popup-container">
|
||||
@ -77,31 +56,16 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{#if shift}
|
||||
<div class="shift-container">
|
||||
<Scroller>
|
||||
{#each shiftValues as value}
|
||||
{#if typeof value === 'number'}
|
||||
{@const numValue = value}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="btn"
|
||||
on:click={() => {
|
||||
const abs = Math.abs(numValue)
|
||||
let shiftedDate = new Date(currentDate.getTime() + numValue * base)
|
||||
if (abs < DAY && abs >= HOUR) shiftedDate = new Date(Date.now() + numValue * base)
|
||||
dispatch('change', shiftedDate)
|
||||
}}
|
||||
>
|
||||
<TimeShiftPresenter value={value * base} />
|
||||
</div>
|
||||
{:else if value === 'divider'}
|
||||
<div class="divider" />
|
||||
{/if}
|
||||
{/each}
|
||||
</Scroller>
|
||||
</div>
|
||||
{/if}
|
||||
<Shifts
|
||||
{currentDate}
|
||||
on:change={(evt) => (currentDate = evt.detail)}
|
||||
{direction}
|
||||
{days}
|
||||
{minutes}
|
||||
{hours}
|
||||
{shift}
|
||||
{mode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@ -110,45 +74,5 @@
|
||||
background: var(--popup-bg-color);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--popup-shadow);
|
||||
|
||||
.shift-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
top: 1rem;
|
||||
right: calc(100% - 0.5rem);
|
||||
bottom: 1rem;
|
||||
// height: fit-content;
|
||||
width: fit-content;
|
||||
width: 12rem;
|
||||
min-width: 12rem;
|
||||
background: var(--popup-bg-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--popup-shadow);
|
||||
z-index: -1;
|
||||
|
||||
.btn {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: transparent;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
background-color: var(--button-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0.25rem 0.75rem 0.25rem 0;
|
||||
height: 1px;
|
||||
min-height: 1px;
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -13,23 +13,22 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { createEventDispatcher, afterUpdate } from 'svelte'
|
||||
import { daysInMonth, getMonthName } from './internal/DateUtils'
|
||||
import ui from '../../plugin'
|
||||
import { dpstore, closeDatePopup } from '../../popups'
|
||||
import Label from '../Label.svelte'
|
||||
import Icon from '../Icon.svelte'
|
||||
import IconClose from '../icons/Close.svelte'
|
||||
import DPCalendar from './icons/DPCalendar.svelte'
|
||||
import DateRangePopup from './DateRangePopup.svelte'
|
||||
import { DateRangeMode } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { afterUpdate, createEventDispatcher } from 'svelte'
|
||||
import ui from '../../plugin'
|
||||
import { showPopup } from '../../popups'
|
||||
import Icon from '../Icon.svelte'
|
||||
import Label from '../Label.svelte'
|
||||
import IconClose from '../icons/Close.svelte'
|
||||
import DatePopup from './DatePopup.svelte'
|
||||
import DPCalendar from './icons/DPCalendar.svelte'
|
||||
import { daysInMonth, getMonthName } from './internal/DateUtils'
|
||||
|
||||
export let value: number | null | undefined = null
|
||||
export let mode: DateRangeMode = DateRangeMode.DATE
|
||||
export let editable: boolean = false
|
||||
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
|
||||
export let labelOver: IntlString | undefined = undefined // label instead of date
|
||||
export let labelNull: IntlString = ui.string.NoDate
|
||||
export let kind: 'no-border' | 'link' | 'secondary' = 'no-border'
|
||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||
@ -45,7 +44,7 @@
|
||||
}
|
||||
const editsType: TEdits[] = ['day', 'month', 'year', 'hour', 'min']
|
||||
const getIndex = (id: TEdits): number => editsType.indexOf(id)
|
||||
const today = new Date(Date.now())
|
||||
const today = new Date()
|
||||
const startDate = new Date(0)
|
||||
const defaultSelected: TEdits = mode === DateRangeMode.TIME ? 'hour' : 'day'
|
||||
|
||||
@ -124,7 +123,6 @@
|
||||
currentDate.setSeconds(0, 0)
|
||||
value = currentDate.getTime()
|
||||
dateToEdits()
|
||||
$dpstore.currentDate = currentDate
|
||||
dispatch('change', value)
|
||||
}
|
||||
|
||||
@ -149,7 +147,6 @@
|
||||
value = null
|
||||
dispatch('change', null)
|
||||
}
|
||||
closeDatePopup()
|
||||
edit = opened = false
|
||||
}
|
||||
|
||||
@ -170,7 +167,6 @@
|
||||
if (!isNull() && edits[2].value > 999) {
|
||||
fixEdits()
|
||||
setCurrentDate(setValue(edits[index].value, currentDate, ed))
|
||||
$dpstore.currentDate = currentDate
|
||||
dateToEdits()
|
||||
}
|
||||
edits = edits
|
||||
@ -191,7 +187,6 @@
|
||||
const val = ev.code === 'ArrowUp' ? edits[index].value + 1 : edits[index].value - 1
|
||||
if (currentDate) {
|
||||
setCurrentDate(setValue(val, currentDate, ed))
|
||||
$dpstore.currentDate = currentDate
|
||||
dateToEdits()
|
||||
}
|
||||
}
|
||||
@ -229,7 +224,7 @@
|
||||
edits.forEach((edit) => {
|
||||
if (edit.el === target) kl = true
|
||||
})
|
||||
if (target === popupComp || target === closeBtn) kl = true
|
||||
if (target === closeBtn) kl = true
|
||||
if (!kl || target === null) closeDP()
|
||||
}
|
||||
|
||||
@ -262,41 +257,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
const _change = (result: any): void => {
|
||||
if (result !== undefined) {
|
||||
setCurrentDate(result)
|
||||
saveDate()
|
||||
}
|
||||
}
|
||||
const _close = (result: any): void => {
|
||||
if (result !== undefined) {
|
||||
if (result !== null) {
|
||||
setCurrentDate(result)
|
||||
saveDate()
|
||||
}
|
||||
closeDP()
|
||||
}
|
||||
}
|
||||
|
||||
const openPopup = (): void => {
|
||||
opened = edit = true
|
||||
$dpstore.currentDate = currentDate
|
||||
$dpstore.anchor = datePresenter
|
||||
$dpstore.onChange = _change
|
||||
$dpstore.onClose = _close
|
||||
$dpstore.component = DateRangePopup
|
||||
$dpstore.shift = !noShift
|
||||
$dpstore.mode = mode
|
||||
}
|
||||
let popupComp: HTMLElement
|
||||
$: if (opened && $dpstore.popup) popupComp = $dpstore.popup
|
||||
$: if (opened && edits[0].el && $dpstore.frendlyFocus === undefined) {
|
||||
const frendlyFocus: HTMLElement[] = []
|
||||
edits.forEach((edit, i) => {
|
||||
if (edit.el) frendlyFocus[i] = edit.el
|
||||
})
|
||||
frendlyFocus.push(closeBtn)
|
||||
$dpstore.frendlyFocus = frendlyFocus
|
||||
showPopup(
|
||||
DatePopup,
|
||||
{
|
||||
currentDate,
|
||||
withTime,
|
||||
noShift,
|
||||
label: labelNull
|
||||
},
|
||||
undefined,
|
||||
saveDate,
|
||||
(result) => {
|
||||
if (result !== undefined) {
|
||||
currentDate = result
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const adaptValue = () => {
|
||||
@ -335,7 +312,7 @@
|
||||
>
|
||||
{#if edits[0].value > -1}
|
||||
{edits[0].value.toString().padStart(2, '0')}
|
||||
{:else}DD{/if}
|
||||
{:else}<Label label={ui.string.DD} />{/if}
|
||||
</span>
|
||||
<span class="separator">.</span>
|
||||
<span
|
||||
@ -348,7 +325,7 @@
|
||||
>
|
||||
{#if edits[1].value > -1}
|
||||
{edits[1].value.toString().padStart(2, '0')}
|
||||
{:else}MM{/if}
|
||||
{:else}<Label label={ui.string.MM} />{/if}
|
||||
</span>
|
||||
<span class="separator">.</span>
|
||||
<span
|
||||
@ -361,7 +338,7 @@
|
||||
>
|
||||
{#if edits[2].value > -1}
|
||||
{edits[2].value.toString().padStart(4, '0')}
|
||||
{:else}YYYY{/if}
|
||||
{:else}<Label label={ui.string.YYYY} />{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if withTime}
|
||||
@ -378,7 +355,7 @@
|
||||
>
|
||||
{#if edits[3].value > -1}
|
||||
{edits[3].value.toString().padStart(2, '0')}
|
||||
{:else}HH{/if}
|
||||
{:else}<Label label={ui.string.HH} />{/if}
|
||||
</span>
|
||||
<span class="separator">:</span>
|
||||
<span
|
||||
@ -391,7 +368,7 @@
|
||||
>
|
||||
{#if edits[4].value > -1}
|
||||
{edits[4].value.toString().padStart(2, '0')}
|
||||
{:else}MM{/if}
|
||||
{:else}<Label label={ui.string.MM} />{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if value}
|
||||
@ -418,24 +395,20 @@
|
||||
<Icon icon={DPCalendar} size={'full'} />
|
||||
</div>
|
||||
{#if value !== undefined && value !== null && value.toString() !== ''}
|
||||
{#if labelOver !== undefined}
|
||||
<Label label={labelOver} />
|
||||
{:else}
|
||||
{#if withDate}
|
||||
{new Date(value).getDate()}
|
||||
{getMonthName(new Date(value), 'short')}
|
||||
{#if new Date(value).getFullYear() !== today.getFullYear()}
|
||||
{new Date(value).getFullYear()}
|
||||
{/if}
|
||||
{/if}
|
||||
{#if withTime}
|
||||
{#if withDate}
|
||||
{new Date(value).getDate()}
|
||||
{getMonthName(new Date(value), 'short')}
|
||||
{#if new Date(value).getFullYear() !== today.getFullYear()}
|
||||
{new Date(value).getFullYear()}
|
||||
{/if}
|
||||
{/if}
|
||||
{#if withTime}
|
||||
{#if withDate}
|
||||
<div class="time-divider" />
|
||||
{/if}
|
||||
{new Date(value).getHours().toString().padStart(2, '0')}
|
||||
<span class="separator">:</span>
|
||||
{new Date(value).getMinutes().toString().padStart(2, '0')}
|
||||
<div class="time-divider" />
|
||||
{/if}
|
||||
{new Date(value).getHours().toString().padStart(2, '0')}
|
||||
<span class="separator">:</span>
|
||||
{new Date(value).getMinutes().toString().padStart(2, '0')}
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="content-color">
|
||||
|
@ -21,9 +21,8 @@
|
||||
export let value: number | null | undefined
|
||||
export let editable: boolean = false
|
||||
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
|
||||
export let labelOver: IntlString | undefined = undefined
|
||||
export let labelNull: IntlString = ui.string.NoDate
|
||||
export let noShift: boolean = false
|
||||
</script>
|
||||
|
||||
<DateRangePresenter bind:value mode={DateRangeMode.DATETIME} {editable} {icon} {labelOver} {labelNull} {noShift} />
|
||||
<DateRangePresenter bind:value mode={DateRangeMode.DATETIME} {editable} {icon} {labelNull} {noShift} />
|
||||
|
115
packages/ui/src/components/calendar/Shifts.svelte
Normal file
115
packages/ui/src/components/calendar/Shifts.svelte
Normal file
@ -0,0 +1,115 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { DateRangeMode } from '@hcengineering/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Scroller from '../Scroller.svelte'
|
||||
import TimeShiftPresenter from '../TimeShiftPresenter.svelte'
|
||||
|
||||
export let currentDate: Date | null
|
||||
export let direction: 'before' | 'after' = 'after'
|
||||
export let minutes: number[] = [5, 15, 30]
|
||||
export let hours: number[] = [1, 2, 4, 8, 12]
|
||||
export let days: number[] = [1, 3, 7, 30]
|
||||
export let shift: boolean = false
|
||||
export let mode: DateRangeMode = DateRangeMode.DATE
|
||||
|
||||
$: withTime = mode !== DateRangeMode.DATE
|
||||
$: withDate = mode !== DateRangeMode.TIME
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: base = direction === 'before' ? -1 : 1
|
||||
const MINUTE = 60 * 1000
|
||||
const HOUR = 60 * MINUTE
|
||||
const DAY = 24 * HOUR
|
||||
|
||||
const shiftValues: (number | string)[] = []
|
||||
|
||||
$: {
|
||||
if (withTime) {
|
||||
shiftValues.push(...minutes.map((m) => m * MINUTE), 'divider', ...hours.map((m) => m * HOUR))
|
||||
}
|
||||
if (withDate) {
|
||||
shiftValues.push('divider', ...days.map((m) => m * DAY))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if shift && currentDate}
|
||||
<div class="shift-container">
|
||||
<Scroller>
|
||||
{#each shiftValues as value}
|
||||
{#if typeof value === 'number'}
|
||||
{@const numValue = value}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="btn"
|
||||
on:click={() => {
|
||||
const curr = new Date().setSeconds(0, 0)
|
||||
const shiftedDate = new Date(curr + numValue * base)
|
||||
dispatch('change', shiftedDate)
|
||||
}}
|
||||
>
|
||||
<TimeShiftPresenter value={value * base} />
|
||||
</div>
|
||||
{:else if value === 'divider'}
|
||||
<div class="divider" />
|
||||
{/if}
|
||||
{/each}
|
||||
</Scroller>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.shift-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
top: 1rem;
|
||||
right: calc(100% - 0.5rem);
|
||||
bottom: 1rem;
|
||||
width: fit-content;
|
||||
width: 12rem;
|
||||
min-width: 12rem;
|
||||
background: var(--popup-bg-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--popup-shadow);
|
||||
z-index: -1;
|
||||
|
||||
.btn {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: transparent;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
background-color: var(--button-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0.25rem 0.75rem 0.25rem 0;
|
||||
height: 1px;
|
||||
min-height: 1px;
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -63,7 +63,6 @@ export { default as ColorPopup } from './components/ColorPopup.svelte'
|
||||
export { default as TextArea } from './components/TextArea.svelte'
|
||||
export { default as TextAreaEditor } from './components/TextAreaEditor.svelte'
|
||||
export { default as Section } from './components/Section.svelte'
|
||||
export { default as DatePickerPopup } from './components/calendar/DatePickerPopup.svelte'
|
||||
export { default as DatePicker } from './components/calendar/DatePicker.svelte'
|
||||
export { default as DateRangePicker } from './components/calendar/DateRangePicker.svelte'
|
||||
export { default as DatePopup } from './components/calendar/DatePopup.svelte'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DateRangeMode } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { ComponentType } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import type {
|
||||
AnyComponent,
|
||||
@ -10,7 +10,6 @@ import type {
|
||||
PopupPositionElement,
|
||||
VerticalAlignment
|
||||
} from './types'
|
||||
import { ComponentType } from 'svelte'
|
||||
|
||||
export interface CompAndProps {
|
||||
id: string
|
||||
@ -88,65 +87,6 @@ export function closePopup (category?: string): void {
|
||||
})
|
||||
}
|
||||
|
||||
interface IDatePopup {
|
||||
component: AnySvelteComponent | ComponentType | undefined
|
||||
currentDate: Date | undefined
|
||||
anchor: HTMLElement | undefined
|
||||
popup: HTMLElement | undefined
|
||||
frendlyFocus: HTMLElement[] | undefined
|
||||
mode?: DateRangeMode
|
||||
onClose?: (result: any) => void
|
||||
onChange?: (result: any) => void
|
||||
shift?: boolean
|
||||
}
|
||||
|
||||
export const dpstore = writable<IDatePopup>({
|
||||
component: undefined,
|
||||
currentDate: undefined,
|
||||
anchor: undefined,
|
||||
popup: undefined,
|
||||
frendlyFocus: undefined,
|
||||
onClose: undefined,
|
||||
onChange: undefined,
|
||||
shift: undefined,
|
||||
mode: undefined
|
||||
})
|
||||
|
||||
export function showDatePopup (
|
||||
component: AnySvelteComponent | ComponentType | undefined,
|
||||
currentDate: Date,
|
||||
anchor?: HTMLElement,
|
||||
popup?: HTMLElement,
|
||||
frendlyFocus?: HTMLElement[] | undefined,
|
||||
onClose?: (result: any) => void,
|
||||
onChange?: (result: any) => void,
|
||||
shift?: boolean
|
||||
): void {
|
||||
dpstore.set({
|
||||
component,
|
||||
currentDate,
|
||||
anchor,
|
||||
popup,
|
||||
frendlyFocus,
|
||||
onClose,
|
||||
onChange,
|
||||
shift
|
||||
})
|
||||
}
|
||||
|
||||
export function closeDatePopup (): void {
|
||||
dpstore.set({
|
||||
component: undefined,
|
||||
currentDate: undefined,
|
||||
anchor: undefined,
|
||||
popup: undefined,
|
||||
frendlyFocus: undefined,
|
||||
onClose: undefined,
|
||||
onChange: undefined,
|
||||
shift: undefined
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
|
@ -25,7 +25,6 @@
|
||||
AnyComponent,
|
||||
CompAndProps,
|
||||
Component,
|
||||
DatePickerPopup,
|
||||
Label,
|
||||
Location,
|
||||
PanelInstance,
|
||||
@ -691,7 +690,6 @@
|
||||
<ActionContext context={{ mode: 'popup' }} />
|
||||
</svelte:fragment>
|
||||
</Popup>
|
||||
<DatePickerPopup />
|
||||
<BrowserNotificatator />
|
||||
{:else if employee}
|
||||
<div class="flex-col-center justify-center h-full flex-grow">
|
||||
|
Loading…
Reference in New Issue
Block a user