mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-13 21:27:46 +02:00
Dashboards (#2208)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user