import '@material/web/icon/icon.js'
import '@material/web/button/elevated-button.js'
import '@operato/data-grist/ox-grist.js'
import '@operato/data-grist/ox-filters-form.js'
import '@operato/data-grist/ox-record-creator.js'
import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
import { PageView } from '@operato/shell'
import { css, html } from 'lit'
import { customElement, property, query } from 'lit/decorators.js'
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
import { client } from '@operato/graphql'
import { i18next, localize } from '@operato/i18n'
import { notify, openPopup } from '@operato/layout'
import { OxPopup, OxPrompt } from '@operato/popup'
import { isMobileDevice } from '@operato/utils'
import { p13n } from '@operato/p13n'
import gql from 'graphql-tag'
import { KpiValueImporter } from './kpi-value-importer'
@customElement('kpi-value-list-page')
export class KpiValueListPage extends p13n(localize(i18next)(ScopedElementsMixin(PageView))) { static styles = [
ScrollbarStyles,
CommonGristStyles,
CommonHeaderStyles,
css`
:host { display: flex;
width: 100%;
--grid-record-emphasized-background-color: #8b0000;
--grid-record-emphasized-color: #ff6b6b;
}
ox-grist { overflow-y: auto;
flex: 1;
}
ox-filters-form { flex: 1;
}
`
]
static get scopedElements() { return { 'kpi-value-importer': KpiValueImporter
}
}
@property({ type: Object }) gristConfig: any
@property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
@query('ox-grist') private grist!: DataGrist
get context() { return { title: i18next.t('title.kpi value list'),
search: { handler: (search: string) => { this.grist.searchText = search
},
value: this.grist.searchText
},
filter: { handler: () => { this.grist.toggleHeadroom()
}
},
help: 'kpi/kpi-value',
actions: [
{ title: '일괄 편집',
action: this._openEditor.bind(this),
icon: 'edit',
style: 'background: var(--md-sys-color-tertiary-container); color: var(--md-sys-color-on-tertiary-container);'
},
{ title: i18next.t('button.save'),
action: this._updateKpiValue.bind(this),
...CommonButtonStyles.save
},
{ title: i18next.t('button.delete'),
action: this._deleteKpiValue.bind(this),
...CommonButtonStyles.delete
}
],
exportable: { name: i18next.t('title.kpi value list'),
data: this.exportHandler.bind(this)
},
importable: { handler: this.importHandler.bind(this)
}
}
}
render() { const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
return html`
`
}
async pageInitialized(lifecycle: any) { this.gristConfig = { list: { fields: [
'kpi',
'version',
'valueDate',
'value',
'score',
'kpiOrgScope',
'inputType',
'source',
'meta',
'createdAt',
'updatedAt',
'creator',
'updater'
],
details: [
'kpi',
'version',
'valueDate',
'value',
'score',
'kpiOrgScope',
'inputType',
'source',
'meta',
'createdAt',
'updatedAt',
'creator',
'updater'
]
},
columns: [
{ type: 'gutter', gutterName: 'sequence' },
{ type: 'gutter', gutterName: 'row-selector', multiple: true },
// KPI Value 재계산 버튼 추가
{ type: 'gutter',
gutterName: 'button',
icon: 'refresh',
title: '재계산',
handlers: { click: (columns, data, column, record, rowIndex) => { this._recalculateKpiValue(record)
}
}
},
{ type: 'string',
name: 'kpiOrgScope',
header: '조직',
record: { editable: false },
width: 120,
renderer: (value, column, record, rowIndex, field) => { return value?.entityName || value?.org || ''
}
},
{ type: 'string',
name: 'kpi',
header: 'KPI',
record: { editable: false, renderer: (v, c, r) => r.kpi?.name },
width: 150
},
{ type: 'number', name: 'version', header: '버전', record: { editable: false }, width: 80 },
{ type: 'string', name: 'valueDate', header: '실적일', record: { editable: true }, width: 120 },
{ type: 'number', name: 'value', header: '실적값', record: { editable: true }, width: 120 },
{ type: 'number', name: 'score', header: '성과점수', record: { editable: false }, width: 120 },
{ type: 'string', name: 'inputType', header: '입력방식', record: { editable: false }, width: 100 },
{ type: 'string', name: 'source', header: '수집출처', record: { editable: false }, width: 120 },
{ type: 'object', name: 'meta', header: '메타', record: { editable: false }, width: 120 },
{ type: 'datetime', name: 'createdAt', header: '생성일', record: { editable: false }, width: 180 },
{ type: 'datetime', name: 'updatedAt', header: '수정일', record: { editable: false }, width: 180 },
{ type: 'resource-object',
name: 'creator',
header: '생성자',
record: { editable: false, renderer: (v, c, r) => r.creator?.name },
width: 120
},
{ type: 'resource-object',
name: 'updater',
header: '수정자',
record: { editable: false, renderer: (v, c, r) => r.updater?.name },
width: 120
}
],
rows: { appendable: false,
selectable: { multiple: true
}
},
sorters: [{ name: 'valueDate' }]
}
}
async pageUpdated(changes: any, lifecycle: any) { if (this.active) { // do something here when this page just became as active
}
}
async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) { const response = await client.query({ query: gql`
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) { responses: kpiValues(filters: $filters, pagination: $pagination, sortings: $sortings) { items { id
kpi { id
name
}
version
valueDate
value
score
inputType
source
meta
kpiOrgScope { id
org
}
updater { id
name
}
updatedAt
creator { id
name
}
createdAt
}
total
}
}
`,
variables: { filters,
pagination: { page, limit },
sortings
}
})
return { total: response.data.responses.total || 0,
records: response.data.responses.items || []
}
}
async _openEditor() { const { KpiValueEditorPage } = await import('./kpi-value-editor-page.js')
await openPopup(html`
<${KpiValueEditorPage}>${KpiValueEditorPage}>
`)
}
async _deleteKpiValue() { if (
await OxPrompt.open({ title: i18next.t('text.are_you_sure'),
text: i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }),
confirmButton: { text: i18next.t('button.confirm') },
cancelButton: { text: i18next.t('button.cancel') }
})
) { const ids = this.grist.selected.map(record => record.id)
if (ids && ids.length > 0) { const response = await client.mutate({ mutation: gql`
mutation ($ids: [String!]!) { deleteKpiValues(ids: $ids)
}
`,
variables: { ids
}
})
if (!response.errors) { this.grist.fetch()
notify({ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
})
}
}
}
}
async _updateKpiValue() { let patches = this.grist.dirtyRecords
if (patches && patches.length) { patches = patches.map(patch => { let patchField: any = patch.id ? { id: patch.id } : {}
const dirtyFields = patch.__dirtyfields__
for (let key in dirtyFields) { patchField[key] = dirtyFields[key].after
}
patchField.cuFlag = patch.__dirty__
return patchField
})
const response = await client.mutate({ mutation: gql`
mutation ($patches: [KpiValuePatch!]!) { updateMultipleKpiValue(patches: $patches) { id
kpi { id
name
}
version
valueDate
value
score
}
}
`,
variables: { patches
}
})
if (!response.errors) { this.grist.fetch()
}
}
}
async _recalculateKpiValue(kpiValue) { try { const response = await client.mutate({ mutation: gql`
mutation ($id: String!) { recalculateKpiValue(id: $id) { id
value
score
valueDate
kpiOrgScope { id
entityName
org
}
}
}
`,
variables: { id: kpiValue.id
}
})
if (!response.errors) { notify({ message: 'KPI Value가 성공적으로 재계산되었습니다.' })
this.grist.fetch()
}
} catch (error) { notify({ message: 'KPI Value 재계산 중 오류가 발생했습니다.' })
}
}
async creationCallback(kpiValue) { try { const response = await client.query({ query: gql`
mutation ($kpiValue: NewKpiValue!) { createKpiValue(kpiValue: $kpiValue) { id
}
}
`,
variables: { kpiValue
},
context: { hasUpload: true
}
})
if (!response.errors) { this.grist.fetch()
document.dispatchEvent(
new CustomEvent('notify', { detail: { message: i18next.t('text.data_created_successfully')
}
})
)
}
return true
} catch (ex) { console.error(ex)
document.dispatchEvent(
new CustomEvent('notify', { detail: { type: 'error',
message: i18next.t('text.error')
}
})
)
return false
}
}
async exportHandler() { const exportTargets = this.grist.selected.length ? this.grist.selected : this.grist.dirtyData.records
const targetFieldSet = new Set(['id', 'name', 'description', 'active'])
return exportTargets.map(kpiValue => { let tempObj = {}
for (const field of targetFieldSet) { tempObj[field] = kpiValue[field]
}
return tempObj
})
}
async importHandler(records) { const popup = openPopup(
html`
{ history.back()
this.grist.fetch()
}}
>
`,
{ backdrop: true,
size: 'large',
title: i18next.t('title.import kpi-value')
}
)
popup.onclosed = () => { this.grist.fetch()
}
}
}