import '@operato/data-grist' import gql from 'graphql-tag' import { css, html, LitElement } from 'lit' import { customElement, property, query } from 'lit/decorators.js' import { DataGrist } from '@operato/data-grist/ox-grist.js' import { buildArgs, client } from '@operato/graphql' import { i18next } from '@operato/i18n' import { isMobileDevice } from '@operato/utils' import { MultiColumnFormStyles } from '@operato/form' import { CommonHeaderStyles } from '@operato/styles' import { FetchOption } from '@operato/data-grist' @customElement('entity-selector') export class EntitySelector extends LitElement { static styles = [ CommonHeaderStyles, MultiColumnFormStyles, css` :host { display: flex; flex-direction: column; background-color: var(--md-sys-color-surface); } ox-grist { flex: 1; } form { position: relative; } [search] { position: absolute; right: 0; } ` ] @property({ type: String }) value?: string @property({ type: Array }) searchFields: any @property({ type: Object }) config: any @property({ type: Object }) data: any @property({ type: String }) queryName!: string @property({ type: Array }) select: any @property({ type: Object }) list: any @property({ type: Object }) basicArgs: any @property({ type: Array }) selectedRecords: any @query('ox-grist') grist!: DataGrist @query('search-form') searchForm!: HTMLFormElement render() { return html` ` } onCancel() { history.back() } onConfirm() { this.dispatchEvent( new CustomEvent('entity-selected', { detail: { entity: this.selected } }) ) } async fetchHandler({ page, limit, sorters = [] }: FetchOption) { const response = await client.query({ query: gql` query { ${this.queryName} (${buildArgs(await this._buildConditions(page, limit, sorters))}) { ${this.getSelectFields()} } } ` }) if (!response.errors) { const records = response.data[this.queryName].items.map(item => { if (this.value === item.id) { this.selectedRecords = [item] item['__selected__'] = true } return item }) const total = response.data[this.queryName].total return { records, total, limit, page } } } async firstUpdated() { this.config = { columns: [ { type: 'gutter', gutterName: 'sequence' }, { type: 'gutter', gutterName: 'row-selector', multiple: false } ], rows: { selectable: { multiple: false }, handlers: { click: 'select-row', dblclick: (columns, data, column, record, rowIndex, field) => { this.onConfirm() } }, appendable: false } } if (this.select && this.select.length > 0) { let _searchFields = this.select.filter(selectField => !selectField.hidden) if (this.list && this.list.fields && this.list.fields.length > 0) { _searchFields = _searchFields.filter(searchField => this.list.fields.indexOf(searchField.name) >= 0) } else { _searchFields = _searchFields.slice(0, 4) } this.searchFields = _searchFields.map(selectField => { const fieldType = (selectField.type && selectField.type.toLowerCase()) || 'string' const numberTypes = ['integer', 'float'] return { label: selectField.header || i18next.t(`field.${selectField.name}`), name: selectField.name, type: fieldType === 'string' ? 'text' : numberTypes.indexOf(fieldType) >= 0 ? 'number' : fieldType === 'boolean' ? 'checkbox' : fieldType, queryName: selectField.queryName, props: fieldType === 'string' ? { searchOper: 'i_like' } : fieldType === 'object' ? { searchOper: 'in' } : { searchOper: 'eq' }, attrs: fieldType === 'boolean' ? ['indeterminated'] : [] } }) this.config = { ...this.config, columns: [ ...this.config.columns, ...this.select.map(selectField => { return { ...selectField, type: selectField.type || 'string', width: selectField.width || 160, header: selectField.header || i18next.t(`field.${selectField.name}`) } }) ] } } else { this.searchFields = [ { label: i18next.t('field.name'), name: 'name', type: 'text', props: { searchOper: 'i_like' } }, { label: i18next.t('field.description'), name: 'description', type: 'text', props: { searchOper: 'i_like' } } ] this.config = { ...this.config, columns: [ ...this.config.columns, { type: 'string', name: 'id', header: i18next.t('field.id'), hidden: true }, { type: 'string', name: 'name', header: i18next.t('field.name'), record: { align: 'left' }, sortable: true, width: 160 }, { type: 'string', name: 'description', header: i18next.t('field.description'), record: { align: 'left' }, sortable: true, width: 300 } ] } } this.config = { ...this.config, list: { ...this.list, fields: this.list && this.list.fields && this.list.fields.length > 0 ? this.list.fields : this.config.columns .filter(column => column.type !== 'gutter') .slice(0, 3) .map(column => column.name) } } await this.updateComplete this.grist && this.grist.focus() } getSelectFields() { if (this.select && this.select.length > 0) { return `items { ${this.select.map(selectField => { return selectField.type === 'object' ? `${selectField.name} { ${selectField.subFields && selectField.subFields.length > 0 ? selectField.subFields.join(' ') : `id name description`} }` : `${selectField.name}` })} } total` } else { return ` items { id name description } total ` } } async _buildConditions(page, limit, sorters) { const queryConditions = { filters: [], ...this.basicArgs } queryConditions.filters = [...queryConditions.filters, ...(await this.searchForm.getQueryFilters())] queryConditions.pagination = { page, limit } queryConditions.sortings = sorters return queryConditions } get selected() { var selected = this.grist.selected return selected && selected.length > 0 ? selected[0] : undefined } }