mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-29 04:55:03 +02:00
TSK-1411 Доработка фильтров с датами (#3151)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -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)
|
||||
}}
|
||||
/>
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user