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 } }