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

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2023-05-08 22:52:49 +07:00
committed by GitHub
parent b0e68ed880
commit 16da75f6d8
17 changed files with 964 additions and 482 deletions
@@ -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}
@@ -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}
@@ -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)
}}
/>
+62
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
*/
+17 -1
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
}
})
+9 -1
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