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 { KpiStatisticImporter } from './kpi-statistic-importer.js'
@customElement('kpi-statistic-list-page')
export class KpiStatisticListPage 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-statistic-importer': KpiStatisticImporter
}
}
@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-statistic list'),
search: { handler: (search: string) => { this.grist.searchText = search
},
value: this.grist.searchText
},
filter: { handler: () => { this.grist.toggleHeadroom()
}
},
help: 'kpi/kpi-statistic',
actions: [
{ title: i18next.t('button.save'),
action: this._updateKpiStatistic.bind(this),
...CommonButtonStyles.save
},
{ title: i18next.t('button.delete'),
action: this._deleteKpiStatistic.bind(this),
...CommonButtonStyles.delete
}
],
exportable: { name: i18next.t('title.kpi-statistic 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', 'valueDate', 'periodType'],
details: ['mean', 'median', 'count', 'updatedAt']
},
columns: [
{ type: 'gutter', gutterName: 'sequence' },
{ type: 'gutter', gutterName: 'row-selector', multiple: true },
{ type: 'resource-object',
name: 'kpi',
header: i18next.t('field.kpi'),
record: { editable: false
},
filter: 'search',
sortable: true,
width: 150
},
{ type: 'string',
name: 'valueDate',
header: i18next.t('field.value_date'),
record: { editable: true
},
filter: true,
sortable: true,
width: 120
},
{ type: 'string',
name: 'periodType',
header: i18next.t('field.period_type'),
record: { editable: true
},
filter: true,
sortable: true,
width: 100
},
{ type: 'number',
name: 'count',
header: i18next.t('field.count'),
record: { editable: false
},
sortable: true,
width: 80
},
{ type: 'number',
name: 'mean',
header: i18next.t('field.mean'),
record: { editable: false
},
sortable: true,
width: 100
},
{ type: 'number',
name: 'median',
header: i18next.t('field.median'),
record: { editable: false
},
sortable: true,
width: 100
},
{ type: 'number',
name: 'minimum',
header: i18next.t('field.minimum'),
record: { editable: false
},
sortable: true,
width: 100
},
{ type: 'number',
name: 'maximum',
header: i18next.t('field.maximum'),
record: { editable: false
},
sortable: true,
width: 100
},
{ type: 'number',
name: 'standardDeviation',
header: i18next.t('field.standard_deviation'),
record: { editable: false
},
sortable: true,
width: 120
},
{ type: 'number',
name: 'lowerFence',
header: i18next.t('field.lower_fence'),
record: { editable: false
},
sortable: true,
width: 100
},
{ type: 'number',
name: 'upperFence',
header: i18next.t('field.upper_fence'),
record: { editable: false
},
sortable: true,
width: 100
},
{ type: 'resource-object',
name: 'updater',
header: i18next.t('field.updater'),
record: { editable: false
},
sortable: true,
width: 120
},
{ type: 'datetime',
name: 'updatedAt',
header: i18next.t('field.updated_at'),
record: { editable: false
},
sortable: true,
width: 180
}
],
rows: { appendable: false,
selectable: { multiple: true
}
},
sorters: [
{ name: 'kpi'
}
]
}
}
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: kpiStatistics(filters: $filters, pagination: $pagination, sortings: $sortings) { items { id
kpi { id
name
}
valueDate
periodType
count
sum
range
mean
median
minimum
maximum
standardDeviation
variance
percentile25
percentile75
iqr
lowerFence
upperFence
additionalStatistics
metadata
updater { id
name
}
updatedAt
}
total
}
}
`,
variables: { filters,
pagination: { page, limit },
sortings
}
})
return { total: response.data.responses.total || 0,
records: response.data.responses.items || []
}
}
async _deleteKpiStatistic() { 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!]!) { deleteKpiStatistics(ids: $ids)
}
`,
variables: { ids
}
})
if (!response.errors) { this.grist.fetch()
notify({ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
})
}
}
}
}
async _updateKpiStatistic() { 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: [KpiStatisticPatch!]!) { updateMultipleKpiStatistic(patches: $patches) { kpi { id
name
}
valueDate
periodType
}
}
`,
variables: { patches
}
})
if (!response.errors) { this.grist.fetch()
}
}
}
async creationCallback(kpiStatistic) { try { const response = await client.query({ query: gql`
mutation ($kpiStatistic: NewKpiStatistic!) { createKpiStatistic(kpiStatistic: $kpiStatistic) { id
}
}
`,
variables: { kpiStatistic
},
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',
'kpi',
'kpiType',
'period',
'startDate',
'endDate',
'count',
'mean',
'median',
'minimum',
'maximum',
'standardDeviation',
'lowerFence',
'upperFence'
])
return exportTargets.map(kpiStatistic => { let tempObj = {}
for (const field of targetFieldSet) { tempObj[field] = kpiStatistic[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-statistic')
}
)
popup.onclosed = () => { this.grist.fetch()
}
}
}