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`
(this.mode = 'GRID')} ?active=${mode == 'GRID'}>grid_on (this.mode = 'LIST')} ?active=${mode == 'LIST'}>format_list_bulleted (this.mode = 'CARD')} ?active=${mode == 'CARD'}>apps
` } 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() } } }