Dashboards (#2208)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov
2022-07-05 12:38:19 +07:00
committed by GitHub
parent e052aa5e41
commit 7807588b88
21 changed files with 495 additions and 15 deletions
@@ -0,0 +1,62 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Timestamp } from '@anticrm/core'
import task from '../plugin'
import { eventToHTMLElement, Label, showPopup } from '@anticrm/ui'
import { TimestampPresenter } from '@anticrm/view-resources'
import CreateFilterPopup from './CreateFilterPopup.svelte'
export let value: Timestamp | undefined
</script>
<button
class="mb-4 ml-10"
on:click={(e) => {
showPopup(CreateFilterPopup, {}, eventToHTMLElement(e), (e) => {
value = e
})
}}
>
{#if value}
<TimestampPresenter {value} />
{:else}
<Label label={task.string.AllTime} />
{/if}
</button>
<style lang="scss">
button {
display: flex;
align-items: center;
width: fit-content;
margin-right: 1px;
padding: 0 0.375rem;
font-size: 0.75rem;
height: 1.5rem;
white-space: nowrap;
color: var(--accent-color);
background-color: var(--noborder-bg-color);
border: 1px solid transparent;
transition-property: border, background-color, color, box-shadow;
transition-duration: 0.15s;
border-radius: 0.25rem;
&:hover {
color: var(--caption-color);
background-color: var(--noborder-bg-hover);
}
}
</style>
@@ -0,0 +1,68 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Timestamp } from '@anticrm/core'
import { closeTooltip, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import task from '../plugin'
import { TimestampPresenter } from '@anticrm/view-resources'
const dispatch = createEventDispatcher()
function click (value: Timestamp | undefined): void {
closeTooltip()
dispatch('close', value)
}
const today = new Date().setHours(0, 0, 0, 0)
function shiftDays (diff: number): number {
return new Date(today).setDate(new Date(today).getDate() - diff)
}
const values = [
shiftDays(1),
shiftDays(7),
shiftDays(14),
shiftDays(30),
shiftDays(90),
shiftDays(180),
shiftDays(365)
]
</script>
<div class="selectPopup">
<div class="scroll">
<div class="box">
<div
class="menu-item"
on:click={() => {
click(undefined)
}}
>
<Label label={task.string.AllTime} />
</div>
{#each values as value, i}
<div
class="menu-item"
on:click={() => {
click(value)
}}
>
<TimestampPresenter {value} />
</div>
{/each}
</div>
</div>
</div>
@@ -0,0 +1,153 @@
<!--
// Copyright © 2022 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 core, { Class, DocumentQuery, Ref, SortingOrder, Timestamp } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import type { DoneState, SpaceWithStates, State, Task } from '@anticrm/task'
import task from '@anticrm/task'
import { BarDashboard, DashboardItem } from '@anticrm/ui'
import { FilterBar } from '@anticrm/view-resources'
import CreateFilter from './CreateFilter.svelte'
export let _class: Ref<Class<Task>>
export let space: Ref<SpaceWithStates>
const client = getClient()
const hieararchy = client.getHierarchy()
let states: State[] = []
const statesQuery = createQuery()
$: updateStates(space)
function updateStates (space: Ref<SpaceWithStates>): void {
statesQuery.query(
task.class.State,
{ space },
(result) => {
states = result
},
{
sort: {
rank: SortingOrder.Ascending
}
}
)
}
let wonStates: Set<Ref<DoneState>> = new Set<Ref<DoneState>>()
const doneStatesQuery = createQuery()
$: updateDoneStates(space)
function updateDoneStates (space: Ref<SpaceWithStates>): void {
doneStatesQuery.query(task.class.DoneState, { space }, (result) => {
wonStates = new Set(result.filter((p) => hieararchy.isDerived(p._class, task.class.WonState)).map((p) => p._id))
})
}
let modified: Timestamp | undefined = undefined
let ids: Ref<Task>[] = []
const txQuery = createQuery()
function updateTxes (_class: Ref<Class<Task>>, space: Ref<SpaceWithStates>, modified: Timestamp | undefined): void {
if (modified === undefined) {
ids = []
return
}
txQuery.query(
core.class.TxCollectionCUD,
{
objectSpace: space,
'tx._class': core.class.TxCreateDoc,
'tx.objectClass': _class,
modifiedOn: { $gte: modified }
},
(result) => {
ids = result.map((p) => p.tx.objectId) as Ref<Task>[]
}
)
}
let items: DashboardItem[] = []
$: updateTxes(_class, space, modified)
const docQuery = createQuery()
$: query = modified
? {
space,
_id: { $in: ids }
}
: { space }
let resultQuery = {
space
}
function updateDocs (_class: Ref<Class<Task>>, states: State[], query: DocumentQuery<Task>): void {
if (states.length === 0) {
return
}
docQuery.query(
_class,
query,
(result) => {
const template: Map<Ref<State>, DashboardItem> = new Map(
states.map((p) => {
return [
p._id,
{
label: p.title,
values: [
{ color: 10, value: 0 },
{ color: 0, value: 0 },
{ color: 11, value: 0 }
]
}
]
})
)
for (const value of result) {
const group = template.get(value.state)
if (group === undefined) continue
if (value.doneState === null) {
group.values[0].value++
} else {
const won = wonStates.has(value.doneState)
if (won === undefined) continue
const index = won ? 1 : 2
group.values[index].value++
}
template.set(value.state, group)
}
items = Array.from(template.values())
},
{
projection: {
state: 1,
doneState: 1
}
}
)
}
$: updateDocs(_class, states, resultQuery)
</script>
<CreateFilter bind:value={modified} />
<FilterBar {_class} {query} on:change={(e) => (resultQuery = e.detail)} />
<div class="ml-10 mt-4">
<BarDashboard {items} />
</div>