TSK-1411 Доработка фильтров с датами (#3151)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-05-08 21:52:49 +06:00 committed by GitHub
parent b0e68ed880
commit 16da75f6d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 964 additions and 482 deletions

View File

@ -914,10 +914,6 @@ export function createModel (builder: Builder): void {
component: view.component.ValueFilter
})
builder.mixin(core.class.TypeDate, core.class.Class, view.mixin.AttributeFilter, {
component: view.component.DateFilter
})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AttributePresenter, {
presenter: tracker.component.PriorityRefPresenter
})

View File

@ -669,7 +669,7 @@ export function createModel (builder: Builder): void {
})
builder.mixin(core.class.TypeDate, core.class.Class, view.mixin.AttributeFilter, {
component: view.component.ValueFilter
component: view.component.DateFilter
})
builder.mixin(core.class.EnumOf, core.class.Class, view.mixin.AttributeFilter, {
@ -681,7 +681,7 @@ export function createModel (builder: Builder): void {
})
builder.mixin(core.class.TypeTimestamp, core.class.Class, view.mixin.AttributeFilter, {
component: view.component.TimestampFilter
component: view.component.DateFilter
})
builder.createDoc(
@ -764,6 +764,93 @@ export function createModel (builder: Builder): void {
view.filter.FilterNestedDontMatch
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.Overdue,
result: view.function.FilterDateOutdated,
disableValueSelector: true
},
view.filter.FilterDateOutdated
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.Today,
result: view.function.FilterDateToday,
disableValueSelector: true
},
view.filter.FilterDateToday
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.ThisWeek,
result: view.function.FilterDateWeek,
disableValueSelector: true
},
view.filter.FilterDateWeek
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.NextWeek,
result: view.function.FilterDateNextWeek,
disableValueSelector: true
},
view.filter.FilterDateNextW
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.ThisMonth,
result: view.function.FilterDateMonth,
disableValueSelector: true
},
view.filter.FilterDateM
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.NextMonth,
result: view.function.FilterDateNextMonth,
disableValueSelector: true
},
view.filter.FilterDateNextM
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.CustomDate,
result: view.function.FilterDateCustom
},
view.filter.FilterDateCustom
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.NotSpecified,
result: view.function.FilterDateNotSpecified,
disableValueSelector: true
},
view.filter.FilterDateNotSpecified
)
classPresenter(builder, core.class.EnumOf, view.component.EnumPresenter, view.component.EnumEditor)
// createAction(

View File

@ -104,6 +104,14 @@ export default mergeIds(viewId, view, {
FilterAfterResult: '' as FilterFunction,
FilterNestedMatchResult: '' as FilterFunction,
FilterNestedDontMatchResult: '' as FilterFunction,
FilterDateOutdated: '' as FilterFunction,
FilterDateToday: '' as FilterFunction,
FilterDateWeek: '' as FilterFunction,
FilterDateNextWeek: '' as FilterFunction,
FilterDateMonth: '' as FilterFunction,
FilterDateNextMonth: '' as FilterFunction,
FilterDateNotSpecified: '' as FilterFunction,
FilterDateCustom: '' as FilterFunction,
ShowEmptyGroups: '' as ViewCategoryAction
}
})

View File

@ -0,0 +1,366 @@
<!--
// 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 { afterUpdate, createEventDispatcher } from 'svelte'
import ui from '../../plugin'
import Icon from '../Icon.svelte'
import Label from '../Label.svelte'
import IconClose from '../icons/Close.svelte'
import { daysInMonth } from './internal/DateUtils'
export let currentDate: Date | null
export let withTime: boolean = false
type TEdits = 'day' | 'month' | 'year' | 'hour' | 'min'
interface IEdits {
id: TEdits
value: number
el?: HTMLElement
}
const editsType: TEdits[] = ['day', 'month', 'year', 'hour', 'min']
const getIndex = (id: TEdits): number => editsType.indexOf(id)
let edits: IEdits[] = editsType.map((edit) => {
return { id: edit, value: -1 }
})
let selected: TEdits | null = 'day'
let startTyping: boolean = false
const dispatch = createEventDispatcher()
const setValue = (val: number, date: Date | null, id: TEdits): Date => {
if (date == null) date = new Date()
switch (id) {
case 'day':
date.setDate(val)
break
case 'month':
date.setMonth(val - 1)
break
case 'year':
date.setFullYear(val)
break
case 'hour':
date.setHours(val)
break
case 'min':
date.setMinutes(val)
break
}
return date
}
const getMaxValue = (date: Date | null, id: TEdits): number => {
if (date == null) date = new Date()
switch (id) {
case 'day':
return daysInMonth(date)
case 'month':
return 12
case 'year':
return 3000
case 'hour':
return 23
case 'min':
return 59
}
}
const getValue = (date: Date, id: TEdits): number => {
switch (id) {
case 'day':
return date.getDate()
case 'month':
return date.getMonth() + 1
case 'year':
return date.getFullYear()
case 'hour':
return date.getHours()
case 'min':
return date.getMinutes()
}
}
const dateToEdits = (currentDate: Date | null): void => {
if (currentDate == null) {
edits.forEach((edit) => {
edit.value = -1
})
} else {
for (const edit of edits) {
edit.value = getValue(currentDate, edit.id)
}
}
edits = edits
}
export const isNull = (currentDate: Date | null, full: boolean = false): boolean => {
dateToEdits(currentDate)
let result: boolean = false
edits.forEach((edit, i) => {
if (edit.value === -1 && full && i > 2) result = true
if (edit.value === -1 && !full && i < 3) result = true
if (i === 0 && edit.value === 0) result = true
if (i === 2 && (edit.value < 1970 || edit.value > 3000)) result = true
})
return result
}
const keyDown = (ev: KeyboardEvent, ed: TEdits): void => {
if (selected === ed) {
const index = getIndex(ed)
if (ev.key >= '0' && ev.key <= '9') {
const shouldNext = !startTyping && selected !== 'year'
const num: number = parseInt(ev.key, 10)
if (startTyping) {
if (num === 0) edits[index].value = 0
else {
edits[index].value = num
}
startTyping = false
} else if (edits[index].value * 10 + num > getMaxValue(currentDate, ed)) {
edits[index].value = getMaxValue(currentDate, ed)
} else {
edits[index].value = edits[index].value * 10 + num
}
if (!isNull(currentDate, false) && !startTyping) {
fixEdits()
currentDate = setValue(edits[index].value, currentDate, ed)
dateToEdits(currentDate)
}
edits = edits
if (selected === 'day' && (shouldNext || edits[0].value > getMaxValue(currentDate, 'day') / 10)) {
selected = 'month'
} else if (selected === 'month' && (shouldNext || edits[1].value > 1)) selected = 'year'
else if (selected === 'year' && withTime && (shouldNext || edits[2].value > 999)) selected = 'hour'
else if (selected === 'hour' && (shouldNext || edits[3].value > 2)) selected = 'min'
}
if (ev.code === 'Enter') {
if (!isNull(currentDate, false)) dispatch('close')
}
if (ev.code === 'Backspace') {
edits[index].value = -1
startTyping = true
}
if (ev.code === 'ArrowUp' || (ev.code === 'ArrowDown' && edits[index].el)) {
if (edits[index].value !== -1) {
const val = ev.code === 'ArrowUp' ? edits[index].value + 1 : edits[index].value - 1
if (currentDate) {
currentDate = setValue(val, currentDate, ed)
dateToEdits(currentDate)
}
}
}
if (ev.code === 'ArrowLeft' && edits[index].el) {
selected = index === 0 ? edits[withTime ? 4 : 2].id : edits[index - 1].id
}
if (ev.code === 'ArrowRight' && edits[index].el) {
selected = index === (withTime ? 4 : 2) ? edits[0].id : edits[index + 1].id
}
if (ev.code === 'Tab') {
if ((ed === 'year' && !withTime) || (ed === 'min' && withTime)) dispatch('save')
}
}
}
const focused = (ed: TEdits): void => {
selected = ed
startTyping = true
}
const clearEdits = (): void => {
edits.forEach((edit) => (edit.value = -1))
if (edits[0].el) edits[0].el.focus()
dispatch('save')
}
const fixEdits = (): void => {
const h: number = edits[3].value === -1 ? 0 : edits[3].value
const m: number = edits[4].value === -1 ? 0 : edits[4].value
currentDate = new Date(edits[2].value, edits[1].value - 1, edits[0].value, h, m)
dispatch('save')
}
$: dateToEdits(currentDate)
$: if (selected && edits[getIndex(selected)].el) edits[getIndex(selected)].el?.focus()
afterUpdate(() => {
if (selected) edits[getIndex(selected)].el?.focus()
})
</script>
<div class="datetime-input">
<div class="flex-row-center">
<span
bind:this={edits[0].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[0].id)}
on:focus={() => focused(edits[0].id)}
on:blur={() => (selected = null)}
>
{#if edits[0].value > -1}
{edits[0].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.DD} />{/if}
</span>
<span class="separator">.</span>
<span
bind:this={edits[1].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[1].id)}
on:focus={() => focused(edits[1].id)}
on:blur={() => (selected = null)}
>
{#if edits[1].value > -1}
{edits[1].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.MM} />{/if}
</span>
<span class="separator">.</span>
<span
bind:this={edits[2].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[2].id)}
on:focus={() => focused(edits[2].id)}
on:blur={() => (selected = null)}
>
{#if edits[2].value > -1}
{edits[2].value.toString().padStart(4, '0')}
{:else}<Label label={ui.string.YYYY} />{/if}
</span>
{#if withTime}
<div class="time-divider" />
<span
bind:this={edits[3].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[3].id)}
on:focus={() => focused(edits[3].id)}
on:blur={() => (selected = null)}
>
{#if edits[3].value > -1}
{edits[3].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.HH} />{/if}
</span>
<span class="separator">:</span>
<span
bind:this={edits[4].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[4].id)}
on:focus={() => focused(edits[4].id)}
on:blur={() => (selected = null)}
>
{#if edits[4].value > -1}
{edits[4].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.MM} />{/if}
</span>
{/if}
</div>
{#if currentDate}
<div
class="close-btn"
tabindex="0"
on:click={() => {
selected = 'day'
startTyping = true
currentDate = null
clearEdits()
}}
on:blur={() => (selected = null)}
>
<Icon icon={IconClose} size={'x-small'} />
</div>
{/if}
</div>
<style lang="scss">
.datetime-input {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
margin: 0;
padding: 0.75rem;
height: 3rem;
font-family: inherit;
font-size: 1rem;
color: var(--theme-content-color);
background-color: var(--theme-bg-color);
border: 1px solid var(--theme-button-border);
border-radius: 0.25rem;
transition: border-color 0.15s ease;
&:hover {
border-color: var(--theme-button-enabled);
}
&:focus-within {
color: var(--theme-caption-color);
border-color: var(--primary-edit-border-color);
}
.close-btn {
display: flex;
justify-content: center;
align-items: center;
margin: 0 0.25rem;
width: 0.75rem;
height: 0.75rem;
color: var(--theme-content-color);
background-color: var(--theme-button-enabled);
outline: none;
border-radius: 50%;
cursor: pointer;
&:hover {
color: var(--theme-caption-color);
background-color: var(--theme-button-hovered);
}
}
.digit {
position: relative;
padding: 0 0.125rem;
height: 1.5rem;
line-height: 1.5rem;
color: var(--theme-caption-color);
outline: none;
border-radius: 0.125rem;
&:focus {
color: var(--primary-button-color);
background-color: var(--primary-button-enabled);
}
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 11000;
cursor: pointer;
}
}
.time-divider {
flex-shrink: 0;
margin: 0 0.25rem;
width: 1px;
min-width: 1px;
height: 0.75rem;
background-color: var(--theme-button-border);
}
.separator {
margin: 0 0.1rem;
}
}
</style>

View File

@ -1,5 +1,5 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
// 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
@ -13,18 +13,17 @@
// limitations under the License.
-->
<script lang="ts">
import { DateRangeMode } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { afterUpdate, createEventDispatcher } from 'svelte'
import { createEventDispatcher } from 'svelte'
import ui from '../../plugin'
import ActionIcon from '../ActionIcon.svelte'
import Button from '../Button.svelte'
import Icon from '../Icon.svelte'
import Label from '../Label.svelte'
import IconClose from '../icons/Close.svelte'
import DateInputBox from './DateInputBox.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
@ -35,203 +34,49 @@
const dispatch = createEventDispatcher()
type TEdits = 'day' | 'month' | 'year' | 'hour' | 'min'
interface IEdits {
id: TEdits
value: number
el?: HTMLElement
}
const editsType: TEdits[] = ['day', 'month', 'year', 'hour', 'min']
const getIndex = (id: TEdits): number => editsType.indexOf(id)
const today: Date = new Date(Date.now())
let selected: TEdits | null = 'day'
let startTyping: boolean = false
let edits: IEdits[] = editsType.map((edit) => {
return { id: edit, value: -1 }
})
let viewDate: Date = currentDate ?? today
let viewDateSec: Date
const getValue = (date: Date | null | undefined, id: TEdits): number => {
if (date == null) date = today
switch (id) {
case 'day':
return date.getDate()
case 'month':
return date.getMonth() + 1
case 'year':
return date.getFullYear()
case 'hour':
return date.getHours()
case 'min':
return date.getMinutes()
}
}
const setValue = (val: number, date: Date, id: TEdits): Date => {
switch (id) {
case 'day':
date.setDate(val)
break
case 'month':
date.setMonth(val - 1)
break
case 'year':
date.setFullYear(val)
break
case 'hour':
date.setHours(val)
break
case 'min':
date.setMinutes(val)
break
}
return date
}
const getMaxValue = (date: Date, id: TEdits): number => {
switch (id) {
case 'day':
return daysInMonth(date)
case 'month':
return 12
case 'year':
return 3000
case 'hour':
return 23
case 'min':
return 59
}
}
const dateToEdits = (): void => {
edits.forEach((edit) => {
edit.value = getValue(currentDate, edit.id)
})
edits = edits
}
const clearEdits = (): void => {
edits.forEach((edit) => (edit.value = -1))
if (edits[0].el) edits[0].el.focus()
}
const fixEdits = (): void => {
const h: number = edits[3].value === -1 ? 0 : edits[3].value
const m: number = edits[4].value === -1 ? 0 : edits[4].value
viewDate = currentDate = new Date(edits[2].value, edits[1].value - 1, edits[0].value, h, m)
}
const isNull = (full: boolean = false): boolean => {
let result: boolean = false
edits.forEach((edit, i) => {
if (edit.value === -1 && full && i > 2) result = true
if (edit.value === -1 && !full && i < 3) result = true
if (i === 0 && edit.value === 0) result = true
if (i === 2 && (edit.value < 1970 || edit.value > 3000)) result = true
})
return result
}
let dateInput: DateInputBox
const saveDate = (withTime: boolean = false): void => {
if (currentDate) {
if (!withTime) {
currentDate.setHours(edits[3].value > 0 ? edits[3].value : 0)
currentDate.setMinutes(edits[4].value > 0 ? edits[4].value : 0)
currentDate.setHours(0)
currentDate.setMinutes(0)
}
currentDate.setSeconds(0, 0)
viewDate = currentDate = currentDate
dateToEdits()
dispatch('update', currentDate)
}
}
const closeDP = (withTime: boolean = false): void => {
if (!isNull()) saveDate(withTime)
if (!dateInput.isNull(currentDate, withTime)) saveDate(withTime)
else {
currentDate = null
dispatch('update', null)
}
dispatch('close')
dispatch('close', currentDate)
}
const keyDown = (ev: KeyboardEvent, ed: TEdits): void => {
if (selected === ed) {
const index = getIndex(ed)
if (ev.key >= '0' && ev.key <= '9') {
const num: number = parseInt(ev.key, 10)
if (startTyping) {
if (num === 0) edits[index].value = 0
else {
edits[index].value = num
startTyping = false
}
} else if (edits[index].value * 10 + num > getMaxValue(viewDate, ed)) {
edits[index].value = getMaxValue(viewDate, ed)
} else {
edits[index].value = edits[index].value * 10 + num
}
if (!isNull(false) && !startTyping) {
fixEdits()
currentDate = setValue(edits[index].value, viewDate, ed)
dateToEdits()
}
edits = edits
if (selected === 'day' && edits[0].value > getMaxValue(viewDate, 'day') / 10) selected = 'month'
else if (selected === 'month' && edits[1].value > 1) selected = 'year'
else if (selected === 'year' && withTime && edits[2].value > 999) selected = 'hour'
else if (selected === 'hour' && edits[3].value > 2) selected = 'min'
}
if (ev.code === 'Enter') {
if (!isNull(false)) closeDP()
}
if (ev.code === 'Backspace') {
edits[index].value = -1
startTyping = true
}
if (ev.code === 'ArrowUp' || (ev.code === 'ArrowDown' && edits[index].el)) {
if (edits[index].value !== -1) {
const val = ev.code === 'ArrowUp' ? edits[index].value + 1 : edits[index].value - 1
if (currentDate) {
currentDate = setValue(val, currentDate, ed)
dateToEdits()
}
}
}
if (ev.code === 'ArrowLeft' && edits[index].el) {
selected = index === 0 ? edits[withTime ? 4 : 2].id : edits[index - 1].id
}
if (ev.code === 'ArrowRight' && edits[index].el) {
selected = index === (withTime ? 4 : 2) ? edits[0].id : edits[index + 1].id
}
if (ev.code === 'Tab') {
if ((ed === 'year' && !withTime) || (ed === 'min' && withTime)) saveDate()
}
}
}
const focused = (ed: TEdits): void => {
selected = ed
startTyping = true
}
const updateDate = (date: Date | null): void => {
if (date) {
currentDate = date
dateToEdits()
closeDP()
}
}
const navigateMonth = (result: any): void => {
if (result) {
if (result.charAt(1) === 'm') viewDate.setMonth(viewDate.getMonth() + (result === '-m' ? -1 : 1))
viewDate.setMonth(viewDate.getMonth() + result)
viewDate = viewDate
}
}
const changeMonth = (date: Date, up: boolean): Date => {
return new Date(date.getFullYear(), date.getMonth() + (up ? 1 : -1), date.getDate())
const changeMonth = (date: Date): Date => {
return new Date(date.getFullYear(), date.getMonth() + 1, 1)
}
if (currentDate) dateToEdits()
$: if (selected && edits[getIndex(selected)].el) edits[getIndex(selected)].el?.focus()
$: if (viewDate) viewDateSec = changeMonth(viewDate, true)
afterUpdate(() => {
if (selected) edits[getIndex(selected)].el?.focus()
})
$: if (viewDate) viewDateSec = changeMonth(viewDate)
</script>
<div class="date-popup-container">
@ -254,91 +99,13 @@
{/if}
</div>
<div class="datetime-input">
<div class="flex-row-center">
<span
bind:this={edits[0].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[0].id)}
on:focus={() => focused(edits[0].id)}
on:blur={() => (selected = null)}
>
{#if edits[0].value > -1}
{edits[0].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.DD} />{/if}
</span>
<span class="separator">.</span>
<span
bind:this={edits[1].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[1].id)}
on:focus={() => focused(edits[1].id)}
on:blur={() => (selected = null)}
>
{#if edits[1].value > -1}
{edits[1].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.MM} />{/if}
</span>
<span class="separator">.</span>
<span
bind:this={edits[2].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[2].id)}
on:focus={() => focused(edits[2].id)}
on:blur={() => (selected = null)}
>
{#if edits[2].value > -1}
{edits[2].value.toString().padStart(4, '0')}
{:else}<Label label={ui.string.YYYY} />{/if}
</span>
{#if withTime}
<div class="time-divider" />
<span
bind:this={edits[3].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[3].id)}
on:focus={() => focused(edits[3].id)}
on:blur={() => (selected = null)}
>
{#if edits[3].value > -1}
{edits[3].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.HH} />{/if}
</span>
<span class="separator">:</span>
<span
bind:this={edits[4].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[4].id)}
on:focus={() => focused(edits[4].id)}
on:blur={() => (selected = null)}
>
{#if edits[4].value > -1}
{edits[4].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.MM} />{/if}
</span>
{/if}
</div>
{#if currentDate}
<div
class="close-btn"
tabindex="0"
on:click={() => {
selected = 'day'
startTyping = true
currentDate = null
clearEdits()
}}
on:blur={() => (selected = null)}
>
<Icon icon={IconClose} size={'x-small'} />
</div>
{/if}
</div>
<DateInputBox
bind:this={dateInput}
bind:currentDate
{withTime}
on:close={() => closeDP(withTime)}
on:save={() => saveDate(withTime)}
/>
<div class="month-group">
<MonthSquare
@ -437,83 +204,4 @@
border-top: 1px solid var(--theme-popup-divider);
}
}
.datetime-input {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
margin: 0;
padding: 0.75rem;
height: 3rem;
font-family: inherit;
font-size: 1rem;
color: var(--theme-content-color);
background-color: var(--theme-bg-color);
border: 1px solid var(--theme-button-border);
border-radius: 0.25rem;
transition: border-color 0.15s ease;
&:hover {
border-color: var(--theme-button-enabled);
}
&:focus-within {
color: var(--theme-caption-color);
border-color: var(--primary-edit-border-color);
}
.close-btn {
display: flex;
justify-content: center;
align-items: center;
margin: 0 0.25rem;
width: 0.75rem;
height: 0.75rem;
color: var(--theme-content-color);
background-color: var(--theme-button-enabled);
outline: none;
border-radius: 50%;
cursor: pointer;
&:hover {
color: var(--theme-caption-color);
background-color: var(--theme-button-hovered);
}
}
.digit {
position: relative;
padding: 0 0.125rem;
height: 1.5rem;
line-height: 1.5rem;
color: var(--theme-caption-color);
outline: none;
border-radius: 0.125rem;
&:focus {
color: var(--primary-button-color);
background-color: var(--primary-button-enabled);
}
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 11000;
cursor: pointer;
}
}
.time-divider {
flex-shrink: 0;
margin: 0 0.25rem;
width: 1px;
min-width: 1px;
height: 0.75rem;
background-color: var(--theme-button-border);
}
.separator {
margin: 0 0.1rem;
}
}
</style>

View File

@ -25,6 +25,7 @@
export let hideNavigator: boolean = false
export let viewUpdate: boolean = true
export let displayedWeeksCount = 6
export let selectedTo: Date | null | undefined = undefined
const dispatch = createEventDispatcher()
@ -33,14 +34,24 @@
const today: Date = new Date(Date.now())
const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
if (viewDate === undefined) viewDate = currentDate ?? today
afterUpdate(() => {
if (currentDate && viewUpdate) viewDate = currentDate
if (viewDate) {
monthYear = capitalizeFirstLetter(getMonthName(viewDate)) + ' ' + viewDate.getFullYear()
firstDayOfCurrentMonth = firstDay(viewDate, mondayStart)
}
monthYear = capitalizeFirstLetter(getMonthName(viewDate)) + ' ' + viewDate.getFullYear()
firstDayOfCurrentMonth = firstDay(viewDate, mondayStart)
})
function inRange (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean {
if (currentDate == null || selectedTo == null) return false
if (areDatesEqual(currentDate, selectedTo)) return false
const startDate = currentDate < selectedTo ? currentDate : selectedTo
const endDate = currentDate > selectedTo ? currentDate : selectedTo
return target > startDate && target < endDate
}
function isSelected (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean {
if (currentDate != null && areDatesEqual(currentDate, target)) return true
if (selectedTo != null && areDatesEqual(selectedTo, target)) return true
return false
}
</script>
<div class="month-container">
@ -52,7 +63,7 @@
class="btn"
on:click={() => {
if (viewUpdate) viewDate.setMonth(viewDate.getMonth() - 1)
dispatch('navigation', '-m')
dispatch('navigation', -1)
}}
>
<div class="icon-btn"><Icon icon={IconNavPrev} size={'full'} /></div>
@ -61,7 +72,7 @@
class="btn"
on:click={() => {
if (viewUpdate) viewDate.setMonth(viewDate.getMonth() + 1)
dispatch('navigation', '+m')
dispatch('navigation', 1)
}}
>
<div class="icon-btn"><Icon icon={IconNavNext} size={'full'} /></div>
@ -80,14 +91,14 @@
{#each [...Array(displayedWeeksCount).keys()] as weekIndex}
{#each [...Array(7).keys()] as dayOfWeek}
{@const wrongM = weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek).getMonth() !== viewDate.getMonth()}
{@const date = weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek)}
{@const wrongM = date.getMonth() !== viewDate.getMonth()}
<div
class="day"
class:weekend={isWeekend(weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek))}
class:today={areDatesEqual(today, weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek))}
class:selected={currentDate &&
weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek).getMonth() === currentDate.getMonth() &&
areDatesEqual(currentDate, weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek))}
class:weekend={isWeekend(date)}
class:today={areDatesEqual(today, date)}
class:selected={isSelected(currentDate, selectedTo, date)}
class:range={inRange(currentDate, selectedTo, date)}
class:wrongMonth={wrongM}
style={`grid-column-start: ${dayOfWeek + 1}; grid-row-start: ${weekIndex + 2};`}
on:click|stopPropagation={(ev) => {
@ -95,7 +106,7 @@
ev.preventDefault()
return
}
viewDate = weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek)
viewDate = new Date(date)
if (currentDate) {
viewDate.setHours(currentDate.getHours())
viewDate.setMinutes(currentDate.getMinutes())
@ -103,7 +114,7 @@
dispatch('update', viewDate)
}}
>
{weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek).getDate()}
{date.getDate()}
</div>
{/each}
{/each}
@ -213,6 +224,11 @@
background-color: var(--primary-bg-color);
}
&.range:not(.wrongMonth) {
color: var(--caption-color);
background-color: var(--primary-button-disabled);
}
&:before {
content: '';
position: absolute;

View File

@ -0,0 +1,209 @@
<!--
// 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 { IntlString } from '@hcengineering/platform'
import { createEventDispatcher } from 'svelte'
import ui from '../../plugin'
import ActionIcon from '../ActionIcon.svelte'
import Button from '../Button.svelte'
import Label from '../Label.svelte'
import IconClose from '../icons/Close.svelte'
import DateInputBox from './DateInputBox.svelte'
import MonthSquare from './MonthSquare.svelte'
export let startDate: Date | null
export let endDate: Date | null
export let label: IntlString
export let mondayStart: boolean = true
const dispatch = createEventDispatcher()
const today: Date = new Date(Date.now())
let viewDate: Date = startDate ?? today
let dateInput: DateInputBox
let endDateInput: DateInputBox
const saveDate = (): void => {
if (startDate) {
startDate.setHours(0, 0, 0, 0)
if (endDate) {
endDate.setHours(0, 0, 0, 0)
if (endDate < startDate) {
const swap = endDate
endDate = startDate
startDate = swap
}
}
viewDate = startDate
dispatch('update', {
startDate,
endDate
})
} else if (endDate) {
startDate = endDate
startDate.setHours(0, 0, 0, 0)
endDate = null
viewDate = startDate
dispatch('update', {
startDate,
endDate
})
}
viewDateSec = changeMonth(startDate, endDate)
}
const closeDP = (): void => {
if (!dateInput.isNull(startDate, false)) saveDate()
else {
startDate = null
endDate = null
dispatch('update', {
startDate,
endDate
})
}
dispatch('close', {
startDate,
endDate
})
}
const updateDate = (date: Date | null): void => {
if (date) {
if (startDate == null) {
startDate = date
} else if (endDate == null) {
if (date < startDate) {
endDate = startDate
startDate = date
} else {
endDate = date
}
} else {
startDate = date
endDate = null
}
}
}
const navigateMonth = (result: any): void => {
if (result) {
viewDate.setMonth(viewDate.getMonth() + result)
viewDate = viewDate
viewDateSec.setMonth(viewDateSec.getMonth() + result)
viewDateSec = viewDateSec
}
}
const changeMonth = (date: Date | null, endDate: Date | null): Date => {
if (date == null) {
date = new Date()
}
if (endDate == null || (date.getMonth() === endDate.getMonth() && date.getFullYear() === endDate.getFullYear())) {
return new Date(date.getFullYear(), date.getMonth() + 1, 1)
}
return new Date(endDate)
}
let viewDateSec: Date = changeMonth(startDate, endDate)
</script>
<div class="date-popup-container">
<div class="header">
<span class="fs-title overflow-label"><Label {label} /></span>
<ActionIcon
icon={IconClose}
size={'small'}
action={() => {
dispatch('close', {})
}}
/>
</div>
<div class="content">
<div class="flex-between">
<div class="w-60">
<DateInputBox bind:this={dateInput} bind:currentDate={startDate} on:close={closeDP} on:save={saveDate} />
</div>
<div class="w-60">
<DateInputBox bind:this={endDateInput} bind:currentDate={endDate} on:close={closeDP} on:save={saveDate} />
</div>
</div>
<div class="month-group">
<MonthSquare
bind:currentDate={startDate}
selectedTo={endDate}
{viewDate}
{mondayStart}
viewUpdate={false}
hideNavigator
on:update={(result) => updateDate(result.detail)}
/>
<MonthSquare
bind:currentDate={endDate}
selectedTo={startDate}
viewDate={viewDateSec}
{mondayStart}
viewUpdate={false}
on:update={(result) => updateDate(result.detail)}
on:navigation={(result) => navigateMonth(result.detail)}
/>
</div>
</div>
<div class="footer">
<Button kind={'primary'} label={ui.string.Save} size={'x-large'} width={'100%'} on:click={() => closeDP()} />
</div>
</div>
<style lang="scss">
.date-popup-container {
display: flex;
flex-direction: column;
min-height: 0;
max-width: calc(100vw - 2rem);
max-height: calc(100vh - 2rem);
width: max-content;
height: max-content;
color: var(--caption-color);
background: var(--theme-popup-color);
border-radius: 0.5rem;
box-shadow: var(--theme-popup-shadow);
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem 1rem 2rem;
border-bottom: 1px solid var(--theme-popup-divider);
}
.content {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 1.5rem 2rem;
min-height: 0;
.month-group {
display: flex;
flex-wrap: nowrap;
margin: 0.5rem -0.5rem 0;
}
}
.footer {
padding: 1rem 2rem;
border-top: 1px solid var(--theme-popup-divider);
}
}
</style>

View File

@ -67,6 +67,7 @@ export { default as Section } from './components/Section.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'
export { default as RangeDatePopup } from './components/calendar/RangeDatePopup.svelte'
export { default as DateRangePopup } from './components/calendar/DateRangePopup.svelte'
export { default as TimePopup } from './components/calendar/TimePopup.svelte'
export { default as DateRangePresenter } from './components/calendar/DateRangePresenter.svelte'

View File

@ -67,6 +67,14 @@
"ShowPreviewOnClick": "Please click to show document index preview...",
"Shown": "Shown",
"Total": "Total",
"ShowEmptyGroups": "Show empty groups"
"ShowEmptyGroups": "Show empty groups",
"Overdue": "Overdue",
"Today": "Today",
"ThisWeek": "This week",
"NextWeek": "Next week",
"ThisMonth": "This month",
"NextMonth": "Next month",
"NotSpecified": "Not specified",
"CustomDate": "Custom date"
}
}

View File

@ -64,6 +64,14 @@
"ShowPreviewOnClick": "Пожалуйста нажмите чтобы увидеть предпросмотр...",
"Shown": "Показано",
"Total": "Всего",
"ShowEmptyGroups": "Показывать пустые группы"
"ShowEmptyGroups": "Показывать пустые группы",
"Overdue": "Overdue",
"Today": "Сегодня",
"ThisWeek": "Эта неделя",
"NextWeek": "Следующая неделя",
"ThisMonth": "Этот месяц",
"NextMonth": "Следующий месяц",
"NotSpecified": "Не указана",
"CustomDate": "Выбранная дата"
}
}

View File

@ -13,138 +13,82 @@
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, FindResult, getObjectValue, Ref, SortingOrder, Space } from '@hcengineering/core'
import core, { Class, Doc, Ref, Space } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import ui, { Button, CheckBox, Label, Loading, resizeObserver } from '@hcengineering/ui'
import { Filter } from '@hcengineering/view'
import { getPresenter } from '../../utils'
import { RangeDatePopup, SelectPopup, showPopup } from '@hcengineering/ui'
import { Filter, FilterMode } from '@hcengineering/view'
import { createEventDispatcher, onMount } from 'svelte'
import view from '../../plugin'
import { createEventDispatcher } from 'svelte'
export let _class: Ref<Class<Doc>>
export let space: Ref<Space> | undefined = undefined
export let filter: Filter
export let onChange: (e: Filter) => void
filter.modes = [view.filter.FilterValueIn, view.filter.FilterValueNin]
const isDate = filter.key.attribute.type._class === core.class.TypeDate
filter.modes = isDate
? [
view.filter.FilterDateOutdated,
view.filter.FilterDateToday,
view.filter.FilterDateWeek,
view.filter.FilterDateNextW,
view.filter.FilterDateM,
view.filter.FilterDateNextM,
view.filter.FilterDateCustom,
view.filter.FilterDateNotSpecified
]
: [view.filter.FilterDateToday, view.filter.FilterDateWeek, view.filter.FilterDateM, view.filter.FilterDateCustom]
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
const client = getClient()
const key = { key: filter.key.key }
const promise = getPresenter(client, filter.key._class, key, key)
let values = new Map<any, number>()
let selectedValues: Set<any> = new Set<any>(filter.value.map((p) => p[0]))
const realValues = new Map<any, Set<any>>()
let objectsPromise: Promise<FindResult<Doc>> | undefined
async function getValues (): Promise<void> {
if (objectsPromise) {
await objectsPromise
}
values.clear()
realValues.clear()
let prefix = ''
const hieararchy = client.getHierarchy()
const attr = hieararchy.getAttribute(filter.key._class, filter.key.key)
if (hieararchy.isMixin(attr.attributeOf)) {
prefix = attr.attributeOf + '.'
}
objectsPromise = client.findAll(
_class,
{ ...(space ? { space } : {}) },
{
sort: { [filter.key.key]: SortingOrder.Ascending },
projection: { [prefix + filter.key.key]: 1, space: 1 }
}
)
const res = await objectsPromise
for (const object of res) {
let asDoc = object
if (hieararchy.isMixin(filter.key._class)) {
asDoc = hieararchy.as(object, filter.key._class)
}
const realValue = getObjectValue(filter.key.key, asDoc)
const d = realValue ? new Date(realValue as number).setHours(0, 0, 0, 0) : undefined
values.set(d, (values.get(d) ?? 0) + 1)
realValues.set(d, (realValues.get(d) ?? new Set()).add(realValue))
}
for (const object of filter.value.map((p) => p[0])) {
if (!values.has(object)) values.set(object, 0)
}
values = values
objectsPromise = undefined
}
function isSelected (value: any, values: Set<any>): boolean {
return values.has(value)
}
function toggle (value: any): void {
if (isSelected(value, selectedValues)) {
selectedValues.delete(value)
} else {
selectedValues.add(value)
}
selectedValues = selectedValues
}
const dispatch = createEventDispatcher()
getValues()
let modes: FilterMode[] = []
client.findAll(view.class.FilterMode, { _id: { $in: filter.modes } }).then((res) => {
modes = res
})
function showPicker () {
showPopup(
RangeDatePopup,
{ label: filter.key.attribute.label, startDate: filter.value[0], endDate: filter.value[1] },
undefined,
(res) => {
const value: Date[] = []
if (res.startDate) {
value.push(res.startDate)
}
if (res.endDate && res.startDate !== res.endDate) {
value.push(res.endDate)
}
filter.value = value
onChange(filter)
dispatch('close')
}
)
}
onMount(() => {
if (filter.mode === view.filter.FilterDateCustom) {
showPicker()
}
})
</script>
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="scroll">
<div class="box">
{#await promise then attribute}
{#if objectsPromise}
<Loading />
{:else}
{#each Array.from(values.keys()) as value}
{@const realValue = [...(realValues.get(value) ?? [])][0]}
<button
class="menu-item"
on:click={() => {
toggle(value)
}}
>
<div class="flex-between w-full">
<div class="flex clear-mins">
<div class="check pointer-events-none">
<CheckBox checked={isSelected(value, selectedValues)} primary />
</div>
{#if value !== undefined}
<svelte:component
this={attribute.presenter}
value={typeof value === 'string' ? realValue : value}
{...attribute.props}
/>
{:else}
<Label label={ui.string.NotSelected} />
{/if}
</div>
<div class="content-dark-color ml-2">
{values.get(value)}
</div>
</div>
</button>
{/each}
{/if}
{/await}
</div>
</div>
<Button
shape={'round'}
label={view.string.Apply}
on:click={() => {
filter.value = Array.from(selectedValues.values()).map((p) => {
return [p, Array.from(realValues.get(p) ?? [])]
})
onChange(filter)
dispatch('close')
{#if filter.mode !== view.filter.FilterDateCustom}
<SelectPopup
value={modes.map((it) => ({ ...it, id: it._id }))}
on:close={(evt) => {
filter.mode = evt.detail
if (filter.mode === view.filter.FilterDateCustom) {
showPicker()
} else {
onChange(filter)
dispatch('close')
}
}}
/>
</div>
{/if}

View File

@ -22,6 +22,7 @@
import { Filter, FilterMode } from '@hcengineering/view'
import { createEventDispatcher, onDestroy } from 'svelte'
import view from '../../plugin'
import ModeSelector from './ModeSelector.svelte'
export let filter: Filter
@ -89,6 +90,22 @@
$: modeValuePromise = getMode(filter.mode)
$: nestedModeValuePromise = filter.nested ? getMode(filter.nested.mode) : undefined
function clickHandler (e: MouseEvent, nested: boolean) {
const curr = nested && filter.nested ? filter.nested : filter
if (curr.modes.length <= 2) {
toggle()
} else {
showPopup(ModeSelector, { filter: curr }, eventToHTMLElement(e), (res) => {
if (nested && filter.nested) {
filter.nested.mode = res
} else {
filter.mode = res
}
dispatch('change')
})
}
}
</script>
<div class="filter-section">
@ -102,8 +119,8 @@
</button>
<button
class="filter-button"
on:click={() => {
toggle()
on:click={(e) => {
clickHandler(e, false)
}}
>
{#await modeValuePromise then mode}

View File

@ -0,0 +1,40 @@
<!--
// 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 { getClient } from '@hcengineering/presentation'
import { SelectPopup } from '@hcengineering/ui'
import { Filter, FilterMode } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import view from '../../plugin'
export let filter: Filter
const client = getClient()
const dispatch = createEventDispatcher()
let modes: FilterMode[] = []
client.findAll(view.class.FilterMode, { _id: { $in: filter.modes } }).then((res) => {
modes = res
})
</script>
<SelectPopup
value={modes.map((it) => ({ ...it, id: it._id }))}
on:close={(evt) => {
dispatch('close', evt.detail)
}}
/>

View File

@ -81,6 +81,68 @@ export async function nestedDontMatchResult (filter: Filter, onUpdate: () => voi
return { $nin: result }
}
export async function dateOutdated (filter: Filter): Promise<ObjQueryType<any>> {
return { $lt: new Date() }
}
export async function dateToday (filter: Filter): Promise<ObjQueryType<any>> {
const todayStart = new Date().setUTCHours(0, 0, 0, 0)
const todayEnd = new Date().setUTCHours(23, 59, 59, 999)
return { $gte: todayStart, $lte: todayEnd }
}
export async function dateWeek (filter: Filter): Promise<ObjQueryType<any>> {
const day = new Date().getDay()
const startDayDiff = day === 0 ? 6 : day - 1
const endDayDiff = 7 - startDayDiff
const weekStart = new Date(new Date().setUTCDate(new Date().getUTCDate() - startDayDiff)).setUTCHours(0, 0, 0, 0)
const weekEnd = new Date(new Date().setUTCDate(new Date().getUTCDate() + endDayDiff)).setUTCHours(23, 59, 59, 999)
return { $gte: weekStart, $lte: weekEnd }
}
export async function dateNextWeek (filter: Filter): Promise<ObjQueryType<any>> {
const day = new Date().getDay()
const startDayDiff = day === 0 ? 6 : day - 1
const endDayDiff = 7 - startDayDiff
const weekStart = new Date(new Date().setUTCDate(new Date().getUTCDate() - startDayDiff + 7)).setUTCHours(0, 0, 0, 0)
const weekEnd = new Date(new Date().setUTCDate(new Date().getUTCDate() + endDayDiff + 7)).setUTCHours(23, 59, 59, 999)
return { $gte: weekStart, $lte: weekEnd }
}
export async function dateMonth (filter: Filter): Promise<ObjQueryType<any>> {
const today = new Date()
const lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0)
const monthStart = new Date(new Date().setUTCDate(1)).setUTCHours(0, 0, 0, 0)
const monthEnd = lastDayOfMonth.setUTCHours(23, 59, 59, 999)
return { $gte: monthStart, $lte: monthEnd }
}
export async function dateNextMonth (filter: Filter): Promise<ObjQueryType<any>> {
const today = new Date()
const lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 2, 0)
const monthStart = new Date(new Date().setUTCMonth(new Date().getUTCMonth() + 1, 1)).setUTCHours(0, 0, 0, 0)
const monthEnd = lastDayOfMonth.setUTCHours(23, 59, 59, 999)
return { $gte: monthStart, $lte: monthEnd }
}
export async function dateNotSpecified (filter: Filter): Promise<ObjQueryType<any>> {
return { $in: [undefined, null] }
}
export async function dateCustom (filter: Filter): Promise<ObjQueryType<any>> {
if (filter.value.length === 1) {
const todayStart = new Date(filter.value[0]).setUTCHours(0, 0, 0, 0)
const todayEnd = new Date(filter.value[0]).setUTCHours(23, 59, 59, 999)
return { $gte: todayStart, $lte: todayEnd }
}
if (filter.value.length === 2) {
const todayStart = new Date(filter.value[0]).setUTCHours(0, 0, 0, 0)
const todayEnd = new Date(filter.value[1]).setUTCHours(23, 59, 59, 999)
return { $gte: todayStart, $lte: todayEnd }
}
return await dateNotSpecified(filter)
}
/**
* @public
*/

View File

@ -77,6 +77,14 @@ import ViewletSettingButton from './components/ViewletSettingButton.svelte'
import {
afterResult,
beforeResult,
dateCustom,
dateMonth,
dateNextMonth,
dateNextWeek,
dateNotSpecified,
dateOutdated,
dateToday,
dateWeek,
nestedDontMatchResult,
nestedMatchResult,
objectInResult,
@ -224,6 +232,14 @@ export default async (): Promise<Resources> => ({
FilterNestedMatchResult: nestedMatchResult,
FilterNestedDontMatchResult: nestedDontMatchResult,
ShowEmptyGroups: showEmptyGroups,
StatusSort: statusSort
StatusSort: statusSort,
FilterDateOutdated: dateOutdated,
FilterDateToday: dateToday,
FilterDateWeek: dateWeek,
FilterDateNextWeek: dateNextWeek,
FilterDateMonth: dateMonth,
FilterDateNextMonth: dateNextMonth,
FilterDateNotSpecified: dateNotSpecified,
FilterDateCustom: dateCustom
}
})

View File

@ -64,7 +64,15 @@ export default mergeIds(viewId, view, {
ShowPreviewOnClick: '' as IntlString,
Shown: '' as IntlString,
ShowEmptyGroups: '' as IntlString,
Total: '' as IntlString
Total: '' as IntlString,
Overdue: '' as IntlString,
Today: '' as IntlString,
ThisWeek: '' as IntlString,
NextWeek: '' as IntlString,
ThisMonth: '' as IntlString,
NextMonth: '' as IntlString,
NotSpecified: '' as IntlString,
CustomDate: '' as IntlString
},
function: {
StatusSort: '' as SortFunc

View File

@ -732,7 +732,15 @@ const view = plugin(viewId, {
FilterBefore: '' as Ref<FilterMode>,
FilterAfter: '' as Ref<FilterMode>,
FilterNestedMatch: '' as Ref<FilterMode>,
FilterNestedDontMatch: '' as Ref<FilterMode>
FilterNestedDontMatch: '' as Ref<FilterMode>,
FilterDateOutdated: '' as Ref<FilterMode>,
FilterDateToday: '' as Ref<FilterMode>,
FilterDateWeek: '' as Ref<FilterMode>,
FilterDateNextW: '' as Ref<FilterMode>,
FilterDateM: '' as Ref<FilterMode>,
FilterDateNextM: '' as Ref<FilterMode>,
FilterDateNotSpecified: '' as Ref<FilterMode>,
FilterDateCustom: '' as Ref<FilterMode>
},
popup: {
PositionElementAlignment: '' as Resource<(e?: Event) => PopupAlignment | undefined>