import '@operato/data-tree'
import '@operato/context/ox-context-page-toolbar.js'
import './kpi-view'
import gql from 'graphql-tag'
import { css, html, PropertyValues } from 'lit'
import { customElement, property, query, state } from 'lit/decorators.js'
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
import { client } from '@operato/graphql'
import { i18next, localize } from '@operato/i18n'
import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
import { CustomAlert, PageView } from '@operato/shell'
import { KpiImporter } from './kpi-importer'
import { KpiView } from './kpi-view'
const SubKpiFragment = gql`
fragment SubKpiFragment on Kpi { id
name
description
active
formula
periodType
scoreFormula
grades
vizType
vizMeta
weight
schedule
scheduleId
timezone
version
isLeaf
parent { id
name
}
updater { id
name
}
updatedAt
creator { id
name
}
createdAt
}
`
@customElement('kpi-tree-page')
export class KpiTreePage extends localize(i18next)(ScopedElementsMixin(PageView)) { static styles = [
CommonHeaderStyles,
ScrollbarStyles,
css`
:host { display: flex;
flex-direction: column;
width: 100%;
overflow: auto;
}
content { flex: 1;
display: flex;
flex-direction: row;
}
div[editor] { width: 400px;
display: flex;
flex-direction: column;
border-left: 1px solid var(--md-sys-color-outline-variant);
}
ox-tree-horizontal { flex: 1;
overflow: auto;
}
kpi-view { flex: 1;
padding: var(--spacing-medium);
background-color: var(--md-sys-color-surface-variant);
overflow: auto;
}
.footer { display: flex;
gap: var(--spacing-small);
padding: var(--spacing-medium);
border-top: 1px solid var(--md-sys-color-outline-variant);
background-color: var(--md-sys-color-surface);
}
.footer button { display: flex;
align-items: center;
gap: var(--spacing-small);
padding: var(--spacing-small) var(--spacing-medium);
border: 1px solid var(--md-sys-color-outline);
border-radius: var(--md-sys-shape-corner-small);
background-color: var(--md-sys-color-surface);
color: var(--md-sys-color-on-surface);
cursor: pointer;
}
.footer button:hover { background-color: var(--md-sys-color-secondary-container);
}
.footer button[disabled] { color: var(--md-sys-color-surface-dim);
background-color: transparent;
cursor: not-allowed;
}
.footer button[done] { background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
}
.footer div[filler] { flex: 1;
}
`
]
static get scopedElements() { return { 'kpi-importer': KpiImporter
}
}
@state() root?: any
@state() selected?: any
@state() kpi?: any
@state() modified: boolean = false
@state() appendable: boolean = false
@query('kpi-view') kpiView!: KpiView
get context() { return { title: i18next.t('title.kpi tree'),
help: 'kpi/kpi',
actions: [
{ icon: 'delete',
title: i18next.t('button.delete'),
action: this.delete.bind(this),
emphasis: { danger: true
}
}
].filter(Boolean),
exportable: { name: i18next.t('title.kpi tree'),
data: this.exportHandler.bind(this)
},
importable: { handler: this.importHandler.bind(this)
},
toolbar: false
}
}
render() { return html`
{ const { name } = e.detail || {}
this.modified = true
this.appendable = name && name !== this.selected?.name
}}
>
`
}
updated(changes: PropertyValues) { if (changes.has('selected')) { this.modified = false
this.kpi = { ...this.selected }
}
}
onSelect(e: CustomEvent) { this.selected = e.detail
this.updateContext()
}
reset() { if (this.kpiView) { this.kpiView.kpi = {}
}
}
async create() { const { id: parentId } = this.selected || {}
const { name, description, formula, active, isLeaf, weight } = this.kpi || {}
const kpi = { name,
description,
formula,
active: active !== false,
isLeaf: isLeaf !== false,
weight: weight || 1
} as any
if (parentId) { kpi.parent = { id: parentId }
}
const response = await client.mutate({ mutation: gql`
mutation ($kpi: NewKpi!) { createKpi(kpi: $kpi) { ...SubKpiFragment
}
}
${SubKpiFragment}
`,
variables: { kpi
}
})
this.selected = response.data.createKpi
this.updateContext()
await this.fetch()
}
async save() { const { id, name, description, formula, active, isLeaf, weight } = this.kpi || {}
if (!id) { await CustomAlert({ type: 'warning',
title: i18next.t('message.kpi_not_selected'),
text: i18next.t('message.select_kpi_first'),
confirmButton: { text: i18next.t('button.confirm') }
})
return
}
const patch = { name,
description,
formula,
active,
isLeaf,
weight
} as any
const response = await client.mutate({ mutation: gql`
mutation ($id: String!, $patch: KpiPatch!) { updateKpi(id: $id, patch: $patch) { ...SubKpiFragment
}
}
${SubKpiFragment}
`,
variables: { id,
patch
}
})
this.selected = response.data.updateKpi
this.fetch()
}
async delete() { if (!this.selected) { await CustomAlert({ type: 'warning',
title: i18next.t('message.kpi_not_selected'),
text: i18next.t('message.select_kpi_first'),
confirmButton: { text: i18next.t('button.confirm') }
})
return
}
const children = this.selected?.children
if (children && children.length > 0) { await CustomAlert({ type: 'warning',
title: i18next.t('message.kpis_with_children_cannot_be_deleted'),
text: i18next.t('message.kpi_having_children_cannot_be_deleted'),
confirmButton: { text: i18next.t('button.confirm') }
})
return
}
if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) { const { id } = this.selected || {}
await client.mutate({ mutation: gql`
mutation ($ids: [String!]!) { deleteKpis(ids: $ids)
}
`,
variables: { ids: [id]
}
})
this.selected = {}
this.updateContext()
await this.fetch()
}
}
async pageInitialized(lifecycle: any) { this.fetch()
}
async pageUpdated(changes: any, lifecycle: any) { if (this.active) { // do something here when this page just became as active
}
}
async fetch() { const response = await client.query({ query: gql`
query { kpiTree { ...SubKpiFragment
children { ...SubKpiFragment
children { ...SubKpiFragment
children { ...SubKpiFragment
children { ...SubKpiFragment
children { ...SubKpiFragment
children { ...SubKpiFragment
children { ...SubKpiFragment
}
}
}
}
}
}
}
}
}
${SubKpiFragment}
`
})
const records = response.data.kpiTree || []
/* 다중루트를 지원하기 위해서 this.root 에 모든 루트를 저장하라. */
this.root = records
}
async exportHandler() { // Export functionality can be implemented here
}
async importHandler(records: any[]) { // Import functionality can be implemented here
}
}