mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-24 17:30:03 +00:00
Dashboards (#2208)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
e052aa5e41
commit
7807588b88
@ -155,6 +155,13 @@ export function createModel (builder: Builder): void {
|
||||
lead.app.Lead
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: lead.class.Lead,
|
||||
descriptor: task.viewlet.Dashboard,
|
||||
options: {},
|
||||
config: []
|
||||
})
|
||||
|
||||
createAction(builder, { ...actionTemplates.archiveSpace, target: lead.class.Funnel })
|
||||
createAction(builder, { ...actionTemplates.unarchiveSpace, target: lead.class.Funnel })
|
||||
|
||||
|
@ -364,6 +364,13 @@ export function createModel (builder: Builder): void {
|
||||
config: []
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: recruit.class.Applicant,
|
||||
descriptor: task.viewlet.Dashboard,
|
||||
options: {},
|
||||
config: []
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, task.mixin.KanbanCard, {
|
||||
card: recruit.component.KanbanCard
|
||||
})
|
||||
|
@ -467,6 +467,17 @@ export function createModel (builder: Builder): void {
|
||||
task.viewlet.Kanban
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
label: task.string.Dashboard,
|
||||
icon: task.icon.Dashboard,
|
||||
component: task.component.Dashboard
|
||||
},
|
||||
task.viewlet.Dashboard
|
||||
)
|
||||
|
||||
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.CollectionEditor, {
|
||||
editor: task.component.Todos
|
||||
})
|
||||
|
@ -56,7 +56,8 @@ export default mergeIds(taskId, task, {
|
||||
Todos: '' as AnyComponent,
|
||||
TodoItemPresenter: '' as AnyComponent,
|
||||
StatusTableView: '' as AnyComponent,
|
||||
TaskHeader: '' as AnyComponent
|
||||
TaskHeader: '' as AnyComponent,
|
||||
Dashboard: '' as AnyComponent
|
||||
},
|
||||
space: {
|
||||
TasksPublic: '' as Ref<Space>
|
||||
|
@ -502,6 +502,7 @@ input.search {
|
||||
.max-h-125 { max-height: 31.25rem; }
|
||||
.max-h-60 { max-height: 15rem; }
|
||||
.max-w-60 { max-width: 15rem; }
|
||||
.max-w-240 { max-width: 60rem; }
|
||||
.clear-mins {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
47
packages/ui/src/components/BarDashboard.svelte
Normal file
47
packages/ui/src/components/BarDashboard.svelte
Normal file
@ -0,0 +1,47 @@
|
||||
<!--
|
||||
// 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 { DashboardItem } from '../types'
|
||||
import MultiProgress from './MultiProgress.svelte'
|
||||
|
||||
export let items: DashboardItem[] = []
|
||||
|
||||
$: max = Math.max(...items.map((p) => p.values.reduce((acc, val) => (acc += val.value), 0)))
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
{#each items as item (item.label)}
|
||||
<div>
|
||||
{item.label}
|
||||
</div>
|
||||
<div class="w-full max-w-240">
|
||||
<MultiProgress {max} values={item.values} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
row-gap: 1rem;
|
||||
column-gap: 1rem;
|
||||
|
||||
grid-template-columns: 1fr 5fr;
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
</style>
|
94
packages/ui/src/components/MultiProgress.svelte
Normal file
94
packages/ui/src/components/MultiProgress.svelte
Normal file
@ -0,0 +1,94 @@
|
||||
<!--
|
||||
// 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 { getPlatformColor } from '../colors'
|
||||
export let values: Progress[]
|
||||
export let min: number = 0
|
||||
export let max: number = 100
|
||||
|
||||
interface Progress {
|
||||
value: number
|
||||
color: number
|
||||
}
|
||||
|
||||
$: filtred = values.filter((p) => p.value > min)
|
||||
|
||||
$: proc = (max - min) / 100
|
||||
|
||||
const width: number[] = []
|
||||
$: width.length = filtred.length
|
||||
|
||||
function getLeft (width: number[], i: number): number {
|
||||
let res = 0
|
||||
for (let index = 0; index < width.length; index++) {
|
||||
if (index === i) break
|
||||
res += width[index]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function getWidth (values: Progress[], i: number): number {
|
||||
let value = values[i].value
|
||||
if (value > max) value = max
|
||||
if (value < min) value = min
|
||||
|
||||
const res = Math.round((value - min) / proc)
|
||||
width[i] = res
|
||||
return res
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#each filtred as item, i}
|
||||
<div
|
||||
class="bar fs-title"
|
||||
class:first={i === 0}
|
||||
class:last={i === filtred.length - 1}
|
||||
style="background-color: {getPlatformColor(item.color)}; left: {getLeft(width, i)}%; width: calc(100% * {proc !==
|
||||
0
|
||||
? getWidth(filtred, i)
|
||||
: 0} / 100);"
|
||||
>
|
||||
{item.value}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
background-color: var(--theme-bg-accent-hover);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
padding-left: 0.5rem;
|
||||
padding-top: 0.125rem;
|
||||
&.first {
|
||||
border-top-left-radius: 0.25rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
&.last {
|
||||
border-top-right-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,14 +1,14 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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.
|
||||
-->
|
||||
@ -25,12 +25,11 @@
|
||||
if (value < min) value = min
|
||||
|
||||
function click (e: MouseEvent) {
|
||||
if (!editable) return
|
||||
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
||||
const x = e.clientX - rect.left
|
||||
const pos = x / rect.width
|
||||
value = (max - min) * pos
|
||||
console.log(`set value to ${value}`)
|
||||
console.log(`max value ${max}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -143,6 +143,7 @@ export { default as FocusHandler } from './components/FocusHandler.svelte'
|
||||
export { default as ListView } from './components/ListView.svelte'
|
||||
export { default as ToggleButton } from './components/ToggleButton.svelte'
|
||||
export { default as ExpandCollapse } from './components/ExpandCollapse.svelte'
|
||||
export { default as BarDashboard } from './components/BarDashboard.svelte'
|
||||
|
||||
export * from './types'
|
||||
export * from './location'
|
||||
|
@ -188,3 +188,14 @@ export interface PopupOptions {
|
||||
direction: string
|
||||
fullSize?: boolean
|
||||
}
|
||||
|
||||
export interface DashboardItem {
|
||||
label: string
|
||||
values: DashboardGroup[]
|
||||
tooltip?: LabelAndProps
|
||||
}
|
||||
|
||||
export interface DashboardGroup {
|
||||
value: number
|
||||
color: number
|
||||
}
|
||||
|
@ -28,4 +28,10 @@
|
||||
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>
|
||||
<ellipse cx="12.1" cy="3.9" rx="2.5" ry="2.5"/>
|
||||
</symbol>
|
||||
<symbol id="dashboard" viewBox="0 0 256 256">
|
||||
<g style="stroke:none;stroke-width:0;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;">
|
||||
<path d="M27.429 72.429a4 4 0 0 1-4-4v-11.8a4 4 0 0 1 8 0v11.8a4 4 0 0 1-4 4zm17.571 0a4 4 0 0 1-4-4V45a4 4 0 0 1 8 0v23.429a4 4 0 0 1-4 4zm17.571 0a4 4 0 0 1-4-4V21.571a4 4 0 0 1 8 0v46.857a4 4 0 0 1-4 4.001z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;" transform="matrix(2.8 0 0 2.8 1.964 1.964)"/>
|
||||
<path d="M77.403 90H12.597C5.651 90 0 84.35 0 77.403V12.597C0 5.651 5.651 0 12.597 0h64.807C84.35 0 90 5.651 90 12.597v64.807C90 84.35 84.35 90 77.403 90zM12.597 8A4.602 4.602 0 0 0 8 12.597v64.807A4.602 4.602 0 0 0 12.597 82h64.807A4.602 4.602 0 0 0 82 77.403V12.597A4.602 4.602 0 0 0 77.403 8H12.597z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;" transform="matrix(2.8 0 0 2.8 1.964 1.964)"/>
|
||||
</g>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.8 KiB |
@ -62,7 +62,7 @@
|
||||
"DoneStatesLost": "Done status / Lost",
|
||||
"AllStates": "All states",
|
||||
"DoneStates": "Done states",
|
||||
"States": "States",
|
||||
"States": "States",
|
||||
"NoDoneState": "Not done",
|
||||
"ManageStatusesWithin": "Manage application statuses within",
|
||||
"ManageProjectStatues": "Manage project statues",
|
||||
@ -74,6 +74,8 @@
|
||||
"CantStatusDeleteError": "There are objects in the given state. Move or delete them first.",
|
||||
"Tasks": "Tasks",
|
||||
"Assigned": "Assigned to me",
|
||||
"TodoItems": "Todos"
|
||||
"TodoItems": "Todos",
|
||||
"Dashboard": "Dashboard",
|
||||
"AllTime": "All time"
|
||||
}
|
||||
}
|
@ -74,6 +74,8 @@
|
||||
"CantStatusDeleteError": "Есть объекты с данным статусом. Сначала переместите или удалите их. ",
|
||||
"Tasks": "Задачи",
|
||||
"Assigned": "Назначения",
|
||||
"TodoItems": "Todos"
|
||||
"TodoItems": "Todos",
|
||||
"Dashboard": "Дашборд",
|
||||
"AllTime": "Все время"
|
||||
}
|
||||
}
|
@ -23,7 +23,8 @@ loadMetadata(task.icon, {
|
||||
TodoCheck: `${icons}#todo-check`,
|
||||
TodoUnCheck: `${icons}#todo-uncheck`,
|
||||
ManageStatuses: `${icons}#manage-statuses`,
|
||||
TaskState: `${icons}#task-state`
|
||||
TaskState: `${icons}#task-state`,
|
||||
Dashboard: `${icons}#dashboard`
|
||||
})
|
||||
|
||||
addStringsLoader(taskId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
|
62
plugins/task-resources/src/components/CreateFilter.svelte
Normal file
62
plugins/task-resources/src/components/CreateFilter.svelte
Normal file
@ -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>
|
153
plugins/task-resources/src/components/Dashboard.svelte
Normal file
153
plugins/task-resources/src/components/Dashboard.svelte
Normal file
@ -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>
|
@ -37,6 +37,7 @@ import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
|
||||
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
|
||||
import Todos from './components/todos/Todos.svelte'
|
||||
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
|
||||
import Dashboard from './components/Dashboard.svelte'
|
||||
|
||||
export { default as AssigneePresenter } from './components/AssigneePresenter.svelte'
|
||||
|
||||
@ -52,6 +53,7 @@ export default async (): Promise<Resources> => ({
|
||||
TaskPresenter,
|
||||
EditIssue,
|
||||
KanbanCard,
|
||||
Dashboard,
|
||||
TemplatesIcon,
|
||||
KanbanView,
|
||||
StatePresenter,
|
||||
|
@ -70,7 +70,8 @@ export default mergeIds(taskId, task, {
|
||||
|
||||
Tasks: '' as IntlString,
|
||||
Assigned: '' as IntlString,
|
||||
Task: '' as IntlString
|
||||
Task: '' as IntlString,
|
||||
AllTime: '' as IntlString
|
||||
},
|
||||
status: {
|
||||
AssigneeRequired: '' as IntlString
|
||||
|
@ -222,7 +222,8 @@ const task = plugin(taskId, {
|
||||
ApplicationLabelTask: '' as IntlString,
|
||||
Projects: '' as IntlString,
|
||||
ManageProjectStatues: '' as IntlString,
|
||||
TodoItems: '' as IntlString
|
||||
TodoItems: '' as IntlString,
|
||||
Dashboard: '' as IntlString
|
||||
},
|
||||
class: {
|
||||
Issue: '' as Ref<Class<Issue>>,
|
||||
@ -245,6 +246,7 @@ const task = plugin(taskId, {
|
||||
},
|
||||
viewlet: {
|
||||
Kanban: '' as Ref<ViewletDescriptor>,
|
||||
Dashboard: '' as Ref<ViewletDescriptor>,
|
||||
StatusTable: '' as Ref<ViewletDescriptor>
|
||||
},
|
||||
icon: {
|
||||
@ -253,7 +255,8 @@ const task = plugin(taskId, {
|
||||
TodoCheck: '' as Asset,
|
||||
TodoUnCheck: '' as Asset,
|
||||
ManageStatuses: '' as Asset,
|
||||
TaskState: '' as Asset
|
||||
TaskState: '' as Asset,
|
||||
Dashboard: '' as Asset
|
||||
},
|
||||
global: {
|
||||
// Global task root, if not attached to some other object.
|
||||
|
@ -105,7 +105,8 @@ export {
|
||||
BooleanEditor,
|
||||
BooleanPresenter,
|
||||
NumberEditor,
|
||||
NumberPresenter
|
||||
NumberPresenter,
|
||||
TimestampPresenter
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
|
Loading…
Reference in New Issue
Block a user