med-table
Version:
Wrapper over table of primeng library for Ministry of Health
1 lines • 105 kB
Source Map (JSON)
{"version":3,"file":"med-table.mjs","sources":["../../../projects/med-table/src/lib/types/cellTypes.ts","../../../projects/med-table/src/lib/types/filterTypes.ts","../../../projects/med-table/src/lib/directives/med-template.directive.ts","../../../projects/med-table/src/lib/services/med-table.service.ts","../../../projects/med-table/src/lib/constants/selectors.ts","../../../projects/med-table/src/lib/services/StickyHeader.ts","../../../projects/med-table/src/lib/mixins/TemplatesMixin.ts","../../../projects/med-table/src/lib/configs/primengTranslations.ts","../../../projects/med-table/src/lib/services/FilterDataHandler/FilterMethods.ts","../../../projects/med-table/src/lib/mixins/PrimengConfigMixin.ts","../../../projects/med-table/src/lib/configs/columnConfigKeys.ts","../../../projects/med-table/src/lib/services/CellConfigsFactory.ts","../../../projects/med-table/src/lib/configs/defaultTableSettings.ts","../../../projects/med-table/src/lib/constants/xlsxFormatConstants.ts","../../../projects/med-table/src/lib/services/SheetsGenerator/CellBuilder.ts","../../../projects/med-table/src/lib/constants/string.ts","../../../projects/med-table/src/lib/services/SheetsGenerator/RowBuilder.ts","../../../projects/med-table/src/lib/services/SheetsGenerator/WorkSheetSettings.ts","../../../projects/med-table/src/lib/utils.ts","../../../projects/med-table/src/lib/services/SheetsGenerator/TableHeaderBuilder.ts","../../../projects/med-table/src/lib/services/SheetsGenerator/SheetsGenerator.ts","../../../projects/med-table/src/lib/services/TableCell.ts","../../../projects/med-table/src/lib/services/FilterDataHandler/filterMethodsMap.ts","../../../projects/med-table/src/lib/services/FilterDataHandler/FilterDataHandler.ts","../../../projects/med-table/src/lib/components/table-head-cell/table-head-cell.component.ts","../../../projects/med-table/src/lib/components/table-head-cell/table-head-cell.component.html","../../../projects/med-table/src/lib/components/filters/filter-select/filter-select.component.ts","../../../projects/med-table/src/lib/components/filters/filter-select/filter-select.component.html","../../../projects/med-table/src/lib/mixins/FormInputMixin.ts","../../../projects/med-table/src/lib/components/forms/date-range/date-range.component.ts","../../../projects/med-table/src/lib/components/forms/date-range/date-range.component.html","../../../projects/med-table/src/lib/components/filters/filter-date/filter-date.component.ts","../../../projects/med-table/src/lib/components/filters/filter-date/filter-date.component.html","../../../projects/med-table/src/lib/components/filters/filter-checkbox/filter-checkbox.component.ts","../../../projects/med-table/src/lib/components/filters/filter-checkbox/filter-checkbox.component.html","../../../projects/med-table/src/lib/components/filters/filter-text/filter-text.component.ts","../../../projects/med-table/src/lib/components/filters/filter-text/filter-text.component.html","../../../projects/med-table/src/lib/components/table-cell/editor-builder.ts","../../../projects/med-table/src/lib/components/table-cell/table-cell.component.ts","../../../projects/med-table/src/lib/components/table-cell/table-cell.component.html","../../../projects/med-table/src/lib/med-table.component.ts","../../../projects/med-table/src/lib/med-table.component.html","../../../projects/med-table/src/lib/med-table.module.ts","../../../projects/med-table/src/public-api.ts","../../../projects/med-table/src/med-table.ts"],"sourcesContent":["export enum CELL_TYPES {\n TEXT = 'text',\n LINK = 'link',\n}\n","export enum FILTER_TYPES {\n TEXT = 'text',\n SELECT = 'select',\n DATE = 'date',\n CHECKBOX = 'checkbox',\n}\n","import { Directive, Input, TemplateRef } from '@angular/core';\n\n@Directive({\n selector: '[mTemplate]'\n})\nexport class MedTemplateDirective {\n @Input() type!: string;\n @Input('mTemplate') name!: string;\n\n constructor(public template: TemplateRef<any>) {}\n\n getType(): string {\n return this.name;\n }\n}\n","import { Injectable } from '@angular/core';\nimport { MedDynamicFormService } from 'med-dynamic-form';\n\nimport { PrimeFilters } from '../types/PrimeFilter';\nimport { MedSelectOption } from '../types/MedSelectOption';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class MedTableService {\n private _filters: PrimeFilters = {};\n private _filterSelectData: Record<string, string[]> = {};\n private _data: Record<string, any>[] = [];\n\n constructor(private dynamicFormService: MedDynamicFormService) {}\n\n setFilterSelectData(data: string[], key: string): void {\n this._filterSelectData[key] = data;\n }\n\n getFilterSelectData(key: string): string[] {\n return this._filterSelectData[key] || [];\n }\n\n setSelectData(data: MedSelectOption<any>[], key: string): void {\n this.dynamicFormService.setSelectData(data, key);\n }\n\n setDatalist(data: string[], key: string): void {\n this.dynamicFormService.setDatalist(data, key);\n }\n\n get filters(): PrimeFilters {\n return this._filters;\n }\n\n set filters(data: PrimeFilters) {\n this._filters = data;\n }\n\n get data(): Record<string, any>[] {\n return this._data;\n }\n\n set data(data: Record<string, any>[]) {\n this._data = data;\n }\n}\n","export const APP_SELECTOR = 'med-table';\n\nexport const SELECTOR_AFTER_WEB_COMPONENT = 'div'; // web component doesn't have a height\nexport const FOOTER_SELECTOR = 'body footer';\n\nexport const TABLE_SELECTOR = `${APP_SELECTOR} p-table .p-datatable-wrapper`;\nexport const PAGINATOR_SELECTOR = `${APP_SELECTOR} p-paginator ${SELECTOR_AFTER_WEB_COMPONENT}`;\nexport const UP_SCROLL_SELECTOR = `${APP_SELECTOR} .med-table__up-scroll`;\n","import {\n FOOTER_SELECTOR,\n TABLE_SELECTOR,\n PAGINATOR_SELECTOR,\n UP_SCROLL_SELECTOR,\n} from '../constants/selectors';\n\nexport class StickyHeader {\n /**\n * should call in \"ngAfterViewInit\" hook\n */\n get tableHeight(): string {\n const MARGINS = 20; // 20px margin app in prod\n const contentHeight = this.headerHeight() + this.footerHeight()\n + this.paginatorHeight() + this.upScrollHeight() + MARGINS;\n\n return `calc(100vh - ${contentHeight}px)`;\n }\n\n private headerHeight(): number {\n const app = document.querySelector<HTMLElement>(TABLE_SELECTOR);\n\n if (!app) return 0;\n\n return app.getBoundingClientRect().top;\n }\n\n private paginatorHeight(): number {\n return this.getElementHeight(PAGINATOR_SELECTOR);\n }\n\n private footerHeight(): number {\n return this.getElementHeight(FOOTER_SELECTOR);\n }\n\n private upScrollHeight(): number {\n return this.getElementHeight(UP_SCROLL_SELECTOR);\n }\n\n private getElementHeight(selector: string): number {\n const block = document.querySelector<HTMLElement>(selector);\n\n if (!block) return 0;\n\n return block.offsetHeight;\n }\n}\n","import { AfterContentInit, Component, ContentChildren, QueryList, TemplateRef } from '@angular/core';\n\nimport { MedTemplateDirective } from '../directives/med-template.directive';\n\n@Component({\n template: '',\n})\nexport class TemplatesMixin implements AfterContentInit {\n @ContentChildren(MedTemplateDirective) templates!: QueryList<MedTemplateDirective>;\n\n rowExpansion?: TemplateRef<any>;\n toolbarTemplate?: TemplateRef<any>;\n tableDataTemplate?: TemplateRef<any>;\n tableHeadTemplate?: TemplateRef<any>;\n paginatorTemplate?: TemplateRef<any>;\n captionTemplate?: TemplateRef<any>;\n\n ngAfterContentInit() {\n this.templates.forEach((item) => {\n switch (item.getType()) {\n case 'toolbar':\n this.toolbarTemplate = item.template;\n break;\n case 'caption':\n this.captionTemplate = item.template;\n break;\n case 'tableHead':\n this.tableHeadTemplate = item.template;\n break;\n case 'tableData':\n this.tableDataTemplate = item.template;\n break;\n case 'paginator':\n this.paginatorTemplate = item.template;\n break;\n case 'rowExpansion':\n this.rowExpansion = item.template;\n break;\n }\n });\n }\n}\n","import { Translation } from 'primeng/api/translation';\n\nexport const PRIMENG_TRANSLATIONS: Translation = {\n 'dayNames': ['Неділя', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\\'ятниця', 'Субота'],\n 'dayNamesShort': ['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],\n 'dayNamesMin': ['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],\n 'monthNames': ['Січень','Лютий','Березень','Квітень','Травень','Червень','Липень','Серпень','Вересень','Жовтень','Листопад','Грудень'],\n 'monthNamesShort': ['Січ.', 'Лют.', 'Бер.', 'Квіт.', 'Трав.', 'Черв.','Лип.', 'Серп.', 'Вер.', 'Жовт.', 'Лис.', 'Гру.'],\n};\n","export class FilterMethods {\n static dateRangeHandler(value?: string, filter?: (Date | undefined)[]): boolean {\n const END_DATE = { HOURS: 23, MINUTES: 59 };\n const [startDate, endDate] = filter || [];\n\n if (!startDate) return true;\n\n if (!value) return false;\n\n const currentDate = new Date(value).getTime();\n\n if (endDate) {\n return startDate.getTime() <= currentDate && currentDate < endDate.setHours(END_DATE.HOURS, END_DATE.MINUTES);\n }\n\n return startDate.getTime() <= currentDate;\n }\n\n static selectHandler(value?: string, filter?: string[]): boolean {\n return !filter ? true : filter.includes(value || '');\n }\n\n static checkboxHandler(value?: any, filter?: boolean): boolean {\n if (filter === null) {\n return true;\n }\n\n return Boolean(value) === filter;\n }\n\n static textHandler(value?: string, filter?: string): boolean {\n const regexp = new RegExp(`${filter?.trim()}`, 'i');\n return regexp.test(value || '');\n }\n}\n","import { FilterService, PrimeNGConfig } from 'primeng/api';\nimport { Component, OnInit } from '@angular/core';\n\nimport { TemplatesMixin } from './TemplatesMixin';\nimport { PRIMENG_TRANSLATIONS } from '../configs/primengTranslations';\nimport { FilterMethods } from \"../services/FilterDataHandler/FilterMethods\";\n\n@Component({\n template: '',\n})\nexport class PrimengConfigMixin extends TemplatesMixin implements OnInit {\n constructor(private primeConfig: PrimeNGConfig, private filterService: FilterService) {\n super();\n }\n\n ngOnInit() {\n this.primeConfig.setTranslation(PRIMENG_TRANSLATIONS);\n\n this.filterService.register('custom-contains', FilterMethods.textHandler);\n }\n}\n","export const CONFIG_KEY_CHILDREN = 'children';\n","import {CellDataConfigLocal} from '../types/CellDataConfigLocal';\nimport {MedTableColumnConfig} from '../types/MedTableColumnConfig';\n\nexport class CellConfigsFactory {\n build(list: MedTableColumnConfig[]): CellDataConfigLocal[] {\n return this.flatChildren(list).map(el => this.cellBuilder(el));\n }\n\n private flatChildren(list: MedTableColumnConfig[]): MedTableColumnConfig[] {\n const newList = list.map(el => el.children ? el.children : el).flat();\n const hasChildren = newList.some(el => el.children);\n\n if (hasChildren) {\n return this.flatChildren(newList)\n }\n\n return newList;\n }\n\n private cellBuilder(el: MedTableColumnConfig): CellDataConfigLocal {\n return {\n ...el,\n key: el.key || '',\n children: undefined,\n }\n }\n}\n","import { MedTableSettingsLocal } from '../types/MedTableSettingsLocal';\n\nexport const DEFAULT_TABLE_SETTINGS: MedTableSettingsLocal = {\n rows: 25,\n lazy: false,\n sticky: true,\n filterDelay: 0,\n totalRecords: 0,\n paginator: true,\n scrollHeight: 'flex',\n colMinWidth: '15rem',\n colMaxWidth: '20rem',\n expandedDataKey: '',\n doubleScrollbar: false,\n showCurrentPageReport: true,\n showGlobalSearchFilter: true,\n emptyMessage: 'Дані відсутні.',\n rowsPerPageOptions: [10,25,50,100],\n currentPageReportTemplate: 'Показано {first} – {last} із {totalRecords} записів',\n};\n","export const FONTS = {\n TIMES: 'Times New Roman',\n};\n\nexport const FONT_SIZES = {\n SMALL: 12,\n MEDIUM: 14,\n LARGE: 16,\n EXTRA_LARGE: 18,\n};\n\nexport const CELL_TYPE_STRING = 's';\n\nexport const DEFAULT_CELL_STYLE = {\n size: FONT_SIZES.SMALL,\n bold: false,\n};\n","import { CellObject } from 'xlsx-js-style';\nimport { FONTS, FONT_SIZES, DEFAULT_CELL_STYLE, CELL_TYPE_STRING } from '../../constants/xlsxFormatConstants';\n\nexport interface ICellBuilder {\n createEmptyCell(): CellObject\n createLinkCell(v: string, link: string): CellObject\n createTextCell(v: string, style?: { size?: number; bold?: boolean }): CellObject\n}\n\nexport class CellBuilder implements ICellBuilder {\n createEmptyCell(): CellObject {\n return this.createTextCell('');\n }\n\n createTextCell(v: string, style?: { size?: number; bold?: boolean }): CellObject {\n const {size, bold} = style || DEFAULT_CELL_STYLE;\n\n return {\n v,\n t: CELL_TYPE_STRING,\n s: {\n font: {\n name: FONTS.TIMES,\n sz: size,\n bold,\n },\n alignment: {\n wrapText: true,\n vertical: 'top',\n },\n },\n };\n }\n\n createLinkCell(v: string, link: string): CellObject {\n return {\n v,\n t: CELL_TYPE_STRING,\n l: {Target: link},\n s: {\n font: {\n name: FONTS.TIMES,\n sz: FONT_SIZES.SMALL,\n underline: true,\n color: {\n rgb: '0563c1',\n },\n },\n alignment: {\n wrapText: true,\n vertical: 'top',\n },\n },\n };\n }\n}\n","export const STRING = {\n EMPTY: '',\n SPACE: ' ',\n DOT: '.',\n ZERO: '0',\n HASHTAG: '#',\n SEMICOLON: ';',\n HYPHEN: '–',\n DASH: '-',\n LINE_BREAK: '\\n',\n DELIMITER: {\n HYPHEN_WITH_SPACE: ' – ',\n COMMA: ', ',\n },\n} as const;\n","import { get } from 'lodash';\nimport { CellObject } from 'xlsx-js-style';\nimport { CellBuilder } from './CellBuilder';\nimport { STRING } from '../../constants/string';\n\nimport { CellDataConfigLocal } from '../../types/CellDataConfigLocal';\n\nconst DEFAULT_VIEW_HANDLER = (data: any) => data;\n\nexport interface IRowBuilder {\n createTableRow(config: CellDataConfigLocal[], row: Record<string, any>): CellObject[]\n}\n\nexport class RowBuilder<RowValueType extends Record<string, any>> implements IRowBuilder {\n private readonly cellBuilder = new CellBuilder();\n\n createTableRow(config: CellDataConfigLocal[], row: RowValueType): CellObject[] {\n return config.map((item) =>\n this.cellBuilder.createTextCell(this.getField(row, item)));\n }\n\n private getField<T>(data: RowValueType, config: CellDataConfigLocal): T {\n const { key, sortKey, defaultValue = STRING.HYPHEN, viewHandler = DEFAULT_VIEW_HANDLER } = config;\n const field = viewHandler(get(data, sortKey || key));\n return field || defaultValue;\n }\n}\n","import { Range } from 'xlsx-js-style'\n\nexport interface IWorkSheetSettings {\n merges: Range[]\n updateRowIndex(): void\n addColMerge(start: number, end: number): void\n addRowMerge(rowspan: number, colIndex: number): void\n}\n\nexport class WorkSheetSettings implements IWorkSheetSettings {\n private _rowIndex = 0;\n private _merges: Range[] = [];\n\n get merges (): Range[] {\n return this._merges;\n }\n\n updateRowIndex (count?: number): void {\n this._rowIndex += count || 1;\n }\n\n addColMerge (start: number, end: number): void {\n // s - start, e - end; r - row, c - column\n this._merges.push({\n s: { r: this._rowIndex, c: start },\n e: { r: this._rowIndex, c: end },\n });\n }\n\n addRowMerge (count: number, colIndex: number): void {\n // s - start, e - end; r - row, c - column\n this._merges.push({\n s: { r: this._rowIndex, c: colIndex },\n e: { r: this._rowIndex + (count - 1), c: colIndex },\n });\n }\n}\n","import { isEqual } from 'lodash';\n\nexport const isExist = (value: any) => value !== undefined && value !== null && value !== '' || Array.isArray(value) && value.length;\n\nexport const deepClone = <R>(data: R): R => JSON.parse(JSON.stringify(data));\n\nexport const getMaxDeepKeyLevel = <T extends Record<string, any>>(data: T[], key: string, deep: number = 1): number => {\n const STEP = 1;\n const levels: number[] = data.map(({ [key]: prop }) => prop ? getMaxDeepKeyLevel(prop, key, deep + STEP) : deep);\n return Math.max(...levels);\n}\n\nexport const getNestedList = <T extends Record<string, any> | undefined>(data: T[], key: string, deep: number): any[] => {\n const STEP = 1;\n if (!deep) return data;\n const mapHandler = (data: T[]) => data.map(el => el ? el[key] : el).flat(deep);\n return deep - STEP ? mapHandler(getNestedList(data, key, deep - STEP)) : mapHandler(data);\n}\n\nexport const getColspanByKey = <T extends Record<string, any>>(column: T, key: string): number => {\n const ONE_COL = 1;\n const { [key]: prop } = column;\n\n if (!prop || !prop.length) return ONE_COL;\n let total = 0;\n\n (prop as T[]).forEach(el => {\n total += el[key] ? getColspanByKey(el, key) : ONE_COL;\n });\n\n return total;\n}\n\nexport const getRowspanByLevelAndKey = <T extends Record<string, any>>(\n data: T,\n key: string,\n deepLevel: number,\n currentLevel: number,\n): number => {\n const DELTA = 1;\n const {[key]: prop = []} = data;\n const levelsToDown = getMaxDeepKeyLevel(prop, key);\n const emptyRows = !currentLevel ? deepLevel - (levelsToDown + DELTA) : 0;\n\n if (!isFinite(levelsToDown)) return deepLevel - currentLevel;\n return deepLevel - (levelsToDown + currentLevel + emptyRows);\n}\n\nexport const getObjectsByLevel = <T extends Record<string, any>>(list: T[], level: number, childKey: keyof T): T[] => {\n if (!level) return list;\n\n const newList: T[] = list.map(el => el[childKey] ? el[childKey] : el).flat() as T[];\n const hasChildren = newList.some(el => el[childKey]);\n\n if (hasChildren) {\n return getObjectsByLevel<T>(newList, --level, childKey);\n }\n\n return newList;\n}\n\nexport const getColumnIndex = <T extends Record<string, any>>(list: T[], obj: T, childKey: keyof T): number => {\n const index = list.findIndex((el) => isEqual(obj, el));\n\n if (index < 0) return 0;\n\n const elementsBeforeCurrent = list.slice(0, index);\n\n return elementsBeforeCurrent\n .map(({ [childKey]: children }) => children ? children.length : 1)\n .reduce<number>((acc, cur) => acc + cur, 0);\n}\n\nexport const createArrayByNumber = (length: number): number[] => Array(length).fill(0).map((x,i) => i);\n","import { isEqual } from 'lodash';\nimport { CellObject } from 'xlsx-js-style';\nimport { CellBuilder } from './CellBuilder';\nimport { IWorkSheetSettings } from './WorkSheetSettings';\nimport { CONFIG_KEY_CHILDREN } from '../../configs/columnConfigKeys';\nimport { MedTableColumnConfig } from '../../types/MedTableColumnConfig';\nimport {\n getNestedList,\n getColumnIndex,\n getColspanByKey,\n getObjectsByLevel,\n getMaxDeepKeyLevel,\n createArrayByNumber,\n} from '../../utils';\n\nexport interface ITableHeaderBuilder {\n getHeaderRows(): CellObject[][]\n}\n\nexport class TableHeaderBuilder implements ITableHeaderBuilder {\n private readonly cellBuilder = new CellBuilder();\n\n constructor(\n private readonly sheetSettings: IWorkSheetSettings,\n private readonly columnsConfig: MedTableColumnConfig[],\n ) {}\n\n private get maxDeepKeyLevel(): number {\n return getMaxDeepKeyLevel<MedTableColumnConfig>(this.columnsConfig, CONFIG_KEY_CHILDREN);\n }\n\n private get tableHeadLevels(): number[] {\n return createArrayByNumber(this.maxDeepKeyLevel);\n }\n\n getHeaderRows(): CellObject[][] {\n return this.tableHeadLevels.map((level, index) => {\n if (index) this.sheetSettings.updateRowIndex();\n\n const allColumnsBeforeLevel = this.getAllColumnsBeforeLevel(level);\n const visibleColumns = this.getVisibleColumns(level);\n\n return allColumnsBeforeLevel.map((column, index) => {\n const cols: CellObject[] = [];\n const range = this.getColspan(column);\n const isVisibleColumn = Boolean(visibleColumns.find(col => isEqual(col, column)));\n const startIndex = getColumnIndex(allColumnsBeforeLevel, column, CONFIG_KEY_CHILDREN);\n\n this.sheetSettings.addColMerge(startIndex, startIndex + range);\n\n if (isVisibleColumn) {\n cols.push(this.cellBuilder.createTextCell(column.label, { bold: true }));\n } else {\n cols.push(this.cellBuilder.createEmptyCell());\n }\n\n createArrayByNumber(range).map(() => {\n cols.push(this.cellBuilder.createEmptyCell());\n });\n\n return cols;\n }).flat();\n });\n }\n\n private getAllColumnsBeforeLevel(level: number): MedTableColumnConfig[] {\n return getObjectsByLevel(this.columnsConfig, level, CONFIG_KEY_CHILDREN);\n }\n\n private getVisibleColumns(level: number): MedTableColumnConfig[] {\n return getNestedList(this.columnsConfig, CONFIG_KEY_CHILDREN, level).filter(Boolean);\n }\n\n private getColspan(column: MedTableColumnConfig): number {\n const DELTA = 1;\n return getColspanByKey(column, CONFIG_KEY_CHILDREN) - DELTA;\n }\n}\n","import * as XLSX from 'xlsx-js-style';\nimport { RowBuilder } from './RowBuilder';\nimport { WorkSheetSettings } from './WorkSheetSettings';\nimport { CellConfigsFactory } from '../CellConfigsFactory';\nimport { CellDataConfigLocal } from '../../types/CellDataConfigLocal';\nimport { MedTableColumnConfig } from '../../types/MedTableColumnConfig';\nimport { ITableHeaderBuilder, TableHeaderBuilder } from './TableHeaderBuilder';\n\nexport interface ISheetsGenerator {\n generate(fileName?: string): void\n}\n\nexport class SheetsGenerator<RowValueType extends Record<string, any>> implements ISheetsGenerator {\n private readonly headerBuilder: ITableHeaderBuilder;\n private readonly rowBuilder = new RowBuilder();\n private readonly dataRowConfig: CellDataConfigLocal[];\n private readonly settings = new WorkSheetSettings();\n\n constructor(private data: RowValueType[], config: MedTableColumnConfig[]) {\n const filteredColumns: MedTableColumnConfig[] = this.hidePrivateFields(config);\n\n this.headerBuilder = new TableHeaderBuilder(this.settings, filteredColumns);\n this.dataRowConfig = new CellConfigsFactory().build(filteredColumns);\n }\n\n generate(fileName: string = 'document'): void {\n const wb = XLSX.utils.book_new();\n const ws = XLSX.utils.aoa_to_sheet([\n this.headerBuilder.getHeaderRows(),\n this.createTableData(),\n ].flat());\n\n ws['!merges'] = this.settings.merges;\n\n XLSX.utils.book_append_sheet(wb, ws);\n XLSX.writeFile(wb, `${fileName}.xlsx`);\n }\n\n private hidePrivateFields(list: MedTableColumnConfig[]): MedTableColumnConfig[] {\n return list.filter(({ hideExport }) => !hideExport).map(column => {\n if (column.children) {\n return {\n ...column,\n children: this.hidePrivateFields(column.children),\n };\n }\n\n return column;\n });\n }\n\n private createTableData(): XLSX.CellObject[][] {\n return this.data.map(row => this.rowBuilder.createTableRow(this.dataRowConfig, row));\n }\n}\n","import { get } from 'lodash';\nimport { STRING } from '../constants/string';\nimport { FILTER_TYPES } from '../types/filterTypes';\nimport { CellDataConfigLocal } from '../types/CellDataConfigLocal';\n\nconst DEFAULT_HANDLER = (data: any) => data;\n\nexport interface ITableCell<T> {\n fieldData: string\n previewData: string\n getValue(data: Record<string, any>, key: string): any\n setItem(data: T): void\n setConfig(config: CellDataConfigLocal): void\n}\n\nexport class TableCell<T extends Record<string, any>> implements ITableCell<T> {\n private _item: T | {} = {};\n private _config: CellDataConfigLocal = {key: '', label: ''};\n\n get fieldData(): string {\n return this.getValue(this._item, this._config.key);\n }\n\n get previewData(): string {\n const { viewHandler = DEFAULT_HANDLER, key, sortKey } = this._config;\n const value = this.getValue(this._item, sortKey || key);\n return value ? viewHandler(value) : this.defaultValue;\n }\n\n private get defaultValue(): any {\n const { defaultValue, filterType } = this._config;\n\n if (defaultValue !== undefined) return defaultValue;\n if (filterType === FILTER_TYPES.CHECKBOX) return 0;\n return STRING.HYPHEN;\n }\n\n getValue(data: Record<string, any>, key: string): any {\n return get(data, key) || '';\n }\n\n setItem(data: T): void {\n this._item = data;\n }\n\n setConfig(config: CellDataConfigLocal): void {\n this._config = config;\n }\n}\n","import { FilterMethods } from './FilterMethods';\nimport { FILTER_TYPES } from '../../types/filterTypes';\n\nexport const filterMethodsMap = new Map<FILTER_TYPES, (value?: string, filter?: any) => boolean>()\n .set(FILTER_TYPES.TEXT, FilterMethods.textHandler)\n .set(FILTER_TYPES.SELECT, FilterMethods.selectHandler)\n .set(FILTER_TYPES.CHECKBOX, FilterMethods.checkboxHandler)\n .set(FILTER_TYPES.DATE, FilterMethods.dateRangeHandler);\n","import {TableCell} from '../TableCell';\nimport {filterMethodsMap} from './filterMethodsMap';\nimport {FILTER_TYPES} from '../../types/filterTypes';\nimport {PrimeFilter, PrimeFilters} from '../../types/PrimeFilter';\nimport {CellDataConfigLocal} from '../../types/CellDataConfigLocal';\n\nexport interface IFilterDataHandler<T extends Record<string, any>> {\n setData(data: T[]): void\n setConfig(config: CellDataConfigLocal[]): void\n filter(filters: PrimeFilters, exitKey?: string): T[]\n}\n\nexport class FilterDataHandler<T extends Record<string, any>> implements IFilterDataHandler<T> {\n private _data: T[] = [];\n private _config: CellDataConfigLocal[] = [];\n\n filter(filters: PrimeFilters, exitKey?: string): T[] {\n return this._data.filter(row => this._config.every((col: CellDataConfigLocal): boolean => {\n const key: string = col.sortKey || col.key;\n const { value }: PrimeFilter = filters[key] || { matchMode: '' };\n\n if (this.isEmptyValue(value, col.filterType) || exitKey === key) return true;\n\n const filterType: FILTER_TYPES = col.filterType || FILTER_TYPES.TEXT;\n const tableCell = new TableCell();\n tableCell.setItem(row);\n tableCell.setConfig(col);\n\n const filterFn = filterMethodsMap.get(filterType);\n return filterFn ? filterFn(tableCell.previewData, value) : true;\n }));\n }\n\n setData(data: T[]): void {\n this._data = data;\n }\n\n setConfig(config: CellDataConfigLocal[]): void {\n this._config = config;\n }\n\n private isEmptyValue(value: any, filterType?: FILTER_TYPES): boolean {\n if (filterType === FILTER_TYPES.SELECT || filterType === FILTER_TYPES.DATE) {\n return !value || Array.isArray(value) && !value.length;\n }\n\n if (filterType === FILTER_TYPES.CHECKBOX) {\n return false;\n }\n\n return !value;\n }\n}\n","import {Component, ElementRef, Input, TemplateRef, ViewChild} from '@angular/core';\n\nimport {MedTableColumnConfig} from '../../types/MedTableColumnConfig';\n\n@Component({\n selector: 'table-head-cell',\n templateUrl: './table-head-cell.component.html',\n styleUrls: ['./table-head-cell.component.scss']\n})\nexport class TableHeadCellComponent {\n @Input() config!: MedTableColumnConfig;\n @Input() template?: TemplateRef<any>;\n\n @ViewChild('contentRef') contentRef!: ElementRef;\n\n get isEmptyTemplate(): boolean {\n // TODO should show default value if slot \"template\" is empty\n const { nativeElement } = this.contentRef || {};\n return !(nativeElement && nativeElement.innerText);\n }\n}\n","<span>\n <span #contentRef>\n <ng-container *ngTemplateOutlet=\"\n template || defaultTemplate;\n context: { $implicit: config }\"\n ></ng-container>\n </span>\n\n <ng-container\n *ngIf=\"isEmptyTemplate\"\n [ngTemplateOutlet]=\"defaultTemplate\"\n ></ng-container>\n</span>\n\n<p-sortIcon\n *ngIf=\"config.label && config.key && !config.hideSortIcon\"\n [field]=\"config.sortKey || config.key\"\n></p-sortIcon>\n\n<ng-template #defaultTemplate>\n {{ config.label }}\n</ng-template>\n","import { get } from 'lodash';\nimport { Component, Input, ViewChild, ElementRef, ChangeDetectorRef, AfterViewInit } from '@angular/core';\n\nimport { MedTableService } from '../../../services/med-table.service';\nimport { MedTableSettingsLocal } from '../../../types/MedTableSettingsLocal';\nimport { IFilterDataHandler } from '../../../services/FilterDataHandler/FilterDataHandler';\n\n@Component({\n selector: 'filter-select',\n templateUrl: './filter-select.component.html',\n styleUrls: ['./filter-select.component.scss']\n})\nexport class FilterSelectComponent<T> implements AfterViewInit {\n constructor(private store: MedTableService, private cd: ChangeDetectorRef) {}\n\n @Input() key!: string;\n @Input() settings!: MedTableSettingsLocal;\n @Input() filterDataHandler!: IFilterDataHandler<T>;\n\n @ViewChild('selectRef') selectRef!: ElementRef;\n\n get filterSelectOptions(): string[] {\n if (this.settings.lazy) {\n return this.store.getFilterSelectData(this.key) || [];\n }\n\n const filteredData = this.filterDataHandler.filter(this.store.filters, this.key);\n const options = filteredData.map(obj => get(obj, this.key)).filter(Boolean);\n return [...new Set(options)]; // delete duplicates\n }\n\n ngAfterViewInit() {\n this.cd.detectChanges();\n }\n\n onFilter({ filter }: Record<string, any>) {\n const regexp = new RegExp(filter, 'i');\n (this.selectRef as any)._filteredOptions = this.filterSelectOptions.filter((value) => regexp.test(value));\n }\n}\n","<p-columnFilter [field]=\"key\" matchMode=\"in\" [showMenu]=\"false\">\n <ng-template pTemplate=\"filter\" let-value let-filter=\"filterCallback\">\n <p-multiSelect\n #selectRef\n placeholder=\"Всі\"\n styleClass=\"filter-select\"\n [ngModel]=\"value\"\n [options]=\"filterSelectOptions\"\n (onChange)=\"filter($event.value)\"\n (onFilter)=\"onFilter($event)\"\n >\n </p-multiSelect>\n </ng-template>\n</p-columnFilter>\n","import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\n\n@Component({\n template: '',\n})\nexport class FormInputMixin<T> implements ControlValueAccessor {\n protected _value: T | any = '';\n\n @ViewChild('input') inputEl!: ElementRef;\n\n @Output() leave = new EventEmitter<void>();\n\n @Input() handler?: (data: any) => any;\n @Input() autoFocus?: boolean;\n @Input() inputId: string = '';\n @Input() set value(val: T) {\n this._value = val;\n this.onChange(this._value);\n }\n\n get value() {\n return this._value;\n }\n\n ngAfterViewInit(): void {\n if (this.autoFocus) {\n setTimeout(() => {\n const { nativeElement, el } = this.inputEl as any;\n if (el === undefined) {\n return nativeElement.focus();\n }\n\n el.nativeElement.querySelector('input').focus();\n }, 0);\n }\n }\n\n onChange(_: T) {}\n\n writeValue(value: T) {\n this.value = value;\n }\n\n registerOnChange(fn: any) {\n this.onChange = fn;\n }\n\n registerOnTouched() {}\n}\n","import { NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { Component, EventEmitter, forwardRef, Output } from '@angular/core';\n\nimport { FormInputMixin } from '../../../mixins/FormInputMixin';\n\n@Component({\n selector: 'date-range',\n templateUrl: './date-range.component.html',\n styleUrls: ['./date-range.component.scss'],\n providers: [{\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DateRangeComponent),\n multi: true\n }]\n})\nexport class DateRangeComponent extends FormInputMixin<Date[]> {\n override _value: Date[] = [];\n\n @Output() select = new EventEmitter<Date[]>();\n}\n","<p-calendar\n #input\n [touchUI]=\"true\"\n [showIcon]=\"true\"\n [inputId]=\"inputId\"\n [(ngModel)]=\"value\"\n dateFormat=\"dd.mm.yy\"\n selectionMode=\"range\"\n appendTo=\"body\"\n (focusout)=\"leave.emit()\"\n (keydown.enter)=\"leave.emit()\"\n (onClose)=\"select.emit(value)\"\n [panelStyle]=\"{'mim-width': '0', 'max-width': '360px'}\"\n></p-calendar>\n\n","import { Component, Input } from '@angular/core';\n\nimport { FilterService, SelectItem } from 'primeng/api';\nimport { FilterMethods } from '../../../services/FilterDataHandler/FilterMethods';\n\nconst dateRangeFilterName = 'date-range';\n\n@Component({\n selector: 'filter-date',\n templateUrl: './filter-date.component.html',\n styleUrls: ['./filter-date.component.scss']\n})\nexport class FilterDateComponent {\n constructor(private filterService: FilterService) {}\n\n @Input() key!: string;\n\n matchModeOptions: SelectItem[] = [\n { label: 'Date range', value: dateRangeFilterName },\n ];\n\n ngOnInit() {\n this.filterService.register(dateRangeFilterName, FilterMethods.dateRangeHandler);\n }\n}\n","<p-columnFilter\n type=\"date\"\n matchMode=\"date-range\"\n [showMenu]=\"false\"\n [field]=\"key\"\n [matchModeOptions]=\"matchModeOptions\"\n>\n <ng-template pTemplate=\"filter\" let-value let-filter=\"filterCallback\">\n <date-range\n [ngModel]=\"value\"\n (select)=\"filter($event)\"\n ></date-range>\n </ng-template>\n</p-columnFilter>\n","import { Component, Input } from '@angular/core';\nimport { FilterService, SelectItem } from 'primeng/api';\nimport { FilterMethods } from '../../../services/FilterDataHandler/FilterMethods';\n\nconst checkboxFilterName = 'bool-exact';\n\n@Component({\n selector: 'filter-checkbox',\n templateUrl: './filter-checkbox.component.html',\n styleUrls: ['./filter-checkbox.component.scss']\n})\nexport class FilterCheckboxComponent {\n constructor(private filterService: FilterService) {}\n\n @Input() key!: string;\n\n matchModeOptions: SelectItem[] = [\n { label: 'Checkbox', value: checkboxFilterName },\n ];\n\n ngOnInit() {\n this.filterService.register(checkboxFilterName, FilterMethods.checkboxHandler);\n }\n}\n","<p-columnFilter\n type=\"boolean\"\n matchMode=\"bool-exact\"\n [field]=\"key\"\n [matchModeOptions]=\"matchModeOptions\"\n></p-columnFilter>\n","import { Component, Input } from '@angular/core';\n\n@Component({\n selector: 'filter-text',\n templateUrl: './filter-text.component.html',\n styleUrls: ['./filter-text.component.scss']\n})\nexport class FilterTextComponent {\n @Input() key!: string;\n}\n","<p-columnFilter\n type=\"text\"\n matchMode=\"contains\"\n [field]=\"key\"\n [showMenu]=\"false\"\n [showClearButton]=\"false\"\n></p-columnFilter>\n","import {FIELD_TYPES, MedFormFieldConfig, PATTERN_TYPES} from 'med-dynamic-form';\nimport {CellDataConfigLocal} from '../../types/CellDataConfigLocal';\n\nexport interface EditorBuilderOptions extends CellDataConfigLocal {\n onLeave: () => void\n}\n\nexport class EditorBuilder implements MedFormFieldConfig {\n key: string;\n editorType: FIELD_TYPES;\n onLeave = () => {};\n autoFocus: boolean = true;\n wrappers: string[] = [];\n inputMask: string = '';\n fieldWrapperSelector: string = '.table-cell__content';\n pattern: PATTERN_TYPES = PATTERN_TYPES.NONE;\n\n constructor(options: EditorBuilderOptions) {\n this.key = options.key;\n this.onLeave = options.onLeave;\n this.editorType = options.editorType || FIELD_TYPES.TEXT;\n this.inputMask = options.inputMask || this.inputMask;\n this.pattern = options.pattern || this.pattern;\n }\n}\n","import {set} from 'lodash';\nimport {AbstractControl, FormBuilder, FormGroup} from '@angular/forms';\nimport {MedFormFieldConfig} from 'med-dynamic-form';\nimport {\n AfterViewInit,\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnInit,\n Output,\n TemplateRef,\n ViewChild,\n} from '@angular/core';\n\nimport {deepClone} from '../../utils';\nimport {STRING} from '../../constants/string';\nimport {EditorBuilder} from './editor-builder';\nimport {CELL_TYPES} from '../../types/cellTypes';\nimport {TableCell} from '../../services/TableCell';\nimport {MedUpdateEvent} from '../../types/MedUpdateEvent';\nimport {CellDataConfigLocal} from '../../types/CellDataConfigLocal';\n\nconst DEFAULT_VISIBLE_EDITOR_HANDLER = () => true;\n\n@Component({\n selector: 'table-cell',\n templateUrl: './table-cell.component.html',\n styleUrls: ['./table-cell.component.scss']\n})\nexport class TableCellComponent<ItemType extends Record<string, any>> implements OnInit, AfterViewInit {\n constructor(private cd: ChangeDetectorRef, private fb: FormBuilder) {}\n\n @Input() template?: TemplateRef<any>;\n\n @Input() set config(newValue: CellDataConfigLocal) {\n this._tableCell.setConfig(newValue);\n this._config = newValue;\n }\n\n get config(): CellDataConfigLocal {\n return this._config;\n }\n\n @Input() set item(newValue: ItemType) {\n this._tableCell.setItem(newValue);\n this._item = newValue;\n }\n\n get item(): ItemType {\n return this._item as ItemType;\n }\n\n @Output() update = new EventEmitter<MedUpdateEvent<ItemType>>();\n @Output() onFocus = new EventEmitter<MedUpdateEvent<ItemType>>();\n\n @ViewChild('contentRef') contentRef!: ElementRef;\n\n readonly cellType = CELL_TYPES;\n fields: MedFormFieldConfig[] = [];\n form: any = new FormGroup({});\n private _item: ItemType | {} = {};\n private _config: CellDataConfigLocal = {key: '', label: ''};\n private _tableCell = new TableCell<ItemType>();\n\n get fieldData(): string {\n return this._tableCell.fieldData;\n }\n\n get previewData(): string {\n return this._tableCell.previewData;\n }\n\n get linkUrl(): string {\n const { url, keyParam } = this.config.linkOptions || {};\n\n if(!url) return STRING.EMPTY;\n\n if (keyParam) {\n const param = this._tableCell.getValue(this.item, keyParam);\n return `${url}?${keyParam}=${param}`;\n }\n\n return url;\n }\n\n get linkTarget(): string {\n const { linkOptions: { target } = {} } = this.config;\n return target ? target : '_self';\n }\n\n get isEditable(): boolean {\n const {editorType, visibleEditorHandler = DEFAULT_VISIBLE_EDITOR_HANDLER} = this.config;\n return Boolean(editorType) && visibleEditorHandler(this.item);\n }\n\n get isEmptyTemplate(): boolean {\n // TODO should show default value if slot \"template\" is empty\n const { nativeElement } = this.contentRef || {};\n return !(nativeElement && nativeElement.innerText);\n }\n\n ngOnInit(): void {\n const { key } = this.config;\n const keyList = key.split(STRING.DOT);\n this.form.addControl(...this.setFormControl(keyList));\n }\n\n ngAfterViewInit() {\n this.cd.detectChanges();\n }\n\n setEditor(): void {\n if (!this.isEditable) {\n return;\n }\n\n const field = new EditorBuilder({\n ...this.config,\n onLeave: this.onLeave.bind(this),\n });\n this.onFocus.emit({item: this.item, key: this.config.key});\n this.fields.push(field);\n }\n\n private onLeave(): void {\n const { key } = this.config;\n const { value } = this.form;\n\n if (this._tableCell.getValue(value, key) !== this.fieldData) {\n const item = set(deepClone(this.item), key, this._tableCell.getValue(value, key));\n this.update.emit({item, key});\n }\n\n this.fields = [];\n }\n\n private setFormControl([firstKey, ...keys]: string[]): [string, AbstractControl] {\n if (!keys.length) return [firstKey, this.fb.control(this.fieldData)];\n\n const [newKey, control] = this.setFormControl(keys);\n return [firstKey, this.fb.group({[newKey]: control})];\n }\n}\n","<div class=\"table-cell__item\">\n <span class=\"table-cell__column-title\">\n {{ config.label }}\n </span>\n\n <span class=\"table-cell__content\">\n <med-dynamic-form\n *ngIf=\"fields.length\"\n [config]=\"fields\"\n [form]=\"form\"\n ></med-dynamic-form>\n\n <div\n [hidden]=\"fields.length\"\n [className]=\"isEditable ? 'table-cell__editable' : ''\"\n (click)=\"setEditor()\"\n >\n <span #contentRef>\n <ng-container *ngTemplateOutlet=\"\n template || defaultTemplate;\n context: { $implicit: previewData, item: item }\"\n ></ng-container>\n </span>\n\n <ng-container\n *ngIf=\"isEmptyTemplate\"\n [ngTemplateOutlet]=\"config.cellType === cellType.LINK ? linkTemplate : defaultTemplate\"\n ></ng-container>\n </div>\n </span>\n</div>\n\n<ng-template #linkTemplate>\n <a [attr.href]=\"linkUrl\" [attr.target]=\"linkTarget\" class=\"table-cell__link\">{{ previewData }}</a>\n</ng-template>\n\n<ng-template #defaultTemplate>\n {{ previewData }}\n</ng-template>\n","import { get } from 'lodash';\nimport { Table } from 'primeng/table';\nimport {FilterService, PrimeNGConfig} from 'primeng/api';\nimport {\n Component,\n Input,\n ViewChild,\n ElementRef,\n Output,\n EventEmitter,\n ChangeDetectorRef,\n AfterViewInit,\n} from '@angular/core';\n\nimport { FILTER_TYPES } from './types/filterTypes';\nimport { APP_SELECTOR } from './constants/selectors';\nimport { StickyHeader } from './services/StickyHeader';\nimport { PrimeOnFilterEvent } from './types/PrimeFilter';\nimport { MedSelectOption } from './types/MedSelectOption';\nimport { MedTableSettings } from './types/MedTableSettings';\nimport { PrimengConfigMixin } from './mixins/PrimengConfigMixin';\nimport { CONFIG_KEY_CHILDREN } from './configs/columnConfigKeys';\nimport { MedTableService } from './services/med-table.service';\nimport { CellDataConfigLocal } from './types/CellDataConfigLocal';\nimport { CellConfigsFactory } from './services/CellConfigsFactory';\nimport { MedUpdateTableEvent } from './types/MedUpdateTableEvent';\nimport { MedTableColumnConfig } from './types/MedTableColumnConfig';\nimport { MedUpdateEvent } from './types/MedUpdateEvent';\nimport { MedTableSettingsLocal } from './types/MedTableSettingsLocal';\nimport { DEFAULT_TABLE_SETTINGS } from './configs/defaultTableSettings';\nimport { PrimeTableLazyLoadEvent } from './types/PrimeTableLazyLoadEvent';\nimport { SheetsGenerator } from './services/SheetsGenerator/SheetsGenerator';\nimport { FilterDataHandler } from './services/FilterDataHandler/FilterDataHandler';\nimport {\n isExist,\n getNestedList,\n getColspanByKey,\n getMaxDeepKeyLevel,\n createArrayByNumber,\n getRowspanByLevelAndKey,\n} from './utils';\n\n@Component({\n selector: APP_SELECTOR,\n templateUrl: 'med-table.component.html',\n styleUrls: ['./med-table.component.scss'],\n})\nexport class MedTableComponent<ItemType> extends PrimengConfigMixin implements AfterViewInit {\n constructor(primeConfig: PrimeNGConfig, filterService: FilterService, private cb: ChangeDetectorRef, private store: MedTableService) {\n super(primeConfig, filterService);\n }\n\n public readonly FILTER_TYPES = FILTER_TYPES;\n public readonly filterDataHandler = new FilterDataHandler<ItemType>();\n public columnsConfig: MedTableColumnConfig[] = [];\n private _data: ItemType[] = [];\n private _config: CellDataConfigLocal[] = [];\n\n @Input() loading: boolean = false;\n @Input() settings: MedTableSettings = {};\n @Input() set config(newValue: MedTableColumnConfig[]) {\n const cellConfigList = new CellConfigsFactory().build(newValue);\n\n this.columnsConfig = newValue;\n this.filterDataHandler.setConfig(cellConfigList);\n this._config = cellConfigList;\n }\n\n get config(): CellDataConfigLocal[] {\n return this._config;\n }\n\n @Input() set data(newValue: ItemType[]) {\n this.store.data = newValue;\n this.filterDataHandler.setData(newValue);\n this._data = newValue;\n }\n\n get data(): ItemType[] {\n return this._data;\n }\n\n @Output() updateTable = new EventEmitter<MedUpdateTableEvent>();\n @Output() updateRow = new EventEmitter<MedUpdateEvent<ItemType>>();\n @Output() focusCell = new EventEmitter<MedUpdateEvent<ItemType>>();\n\n @ViewChild('tableRef') tableRef!: Table;\n @ViewChild('upScrollRef') upScrollRef!: ElementRef<HTMLElement>;\n @ViewChild('upScrollItemRef') upScrollItemRef!: ElementRef<HTMLElement>;\n\n get filterableFields(): string[] {\n return this.config\n .filter(({filterable}) => filterable)\n .map(({key, sortKey}) => sortKey || key);\n }\n\n get localSettings(): MedTableSettingsLocal {\n return {\n ...DEFAULT_TABLE_SETTINGS,\n ...this.settings,\n };\n }\n\n get colspan(): number {\n if (this.localSettings.expandedDataKey) {\n return this.config.length + 1;\n }\n\n return this.config.length;\n }\n\n get maxDeepKeyLevel(): number {\n return getMaxDeepKeyLevel<MedTableColumnConfig>(this.columnsConfig, CONFIG_KEY_CHILDREN);\n }\n\n get tableHeadLevels(): number[] {\n return createArrayByNumber(this.maxDeepKeyLevel);\n }\n\n ngAfterViewInit() {\n if (this.localSettings.doubleScrollbar) {\n this.addDoubleScrollbar();\n }\n\n if (this.localSettings.sticky) {\n this.updateDynamicHeight();\n }\n\n this.cb.detectChanges();\n }\n\n public clearFilters(): void {\n this.store.filters = {};\n this.tableRef.clear();\n }\n\n public exportData(data?: ItemType[]): void {\n const sheetsGenerator = new SheetsGenerator(data || this.tableRef.filteredValue || this.data, this.columnsConfig);\n sheetsGenerator.generate(this.localSettings.exportFileName);\n }\n\n /**\n * Call this method when updated dynamic height of outside content\n */\n public updateDynamicHeight(): void {\n setTimeout(() => {\n const { tableHeight } = new StickyHeader();\n this.settings.scrollHeight = tableHeight;\n }, 0);\n }\n\n getHeadColumnClasses({ headCellClass, sticky }: MedTableColumnConfig): string {\n return `${headCellClass || ''} ${ sticky ? `sticky ${sticky}` : '' }`;\n }\n\n getDataColumnClasses({ dataCellClass, sticky }: MedTableColumnConfig, row: ItemType): string {\n const cellClass = dataCellClass ? dataCellClass(row) : '';\n return `${cellClass} ${ sticky ? `sticky ${sticky}` : '' }`;\n }\n\n getHeadRowConfigByLevel(level: number): Array<MedTableColumnConfig | undefined> {\n return getNestedList(this.columnsConfig, CONFIG_KEY_CHILDREN, level);\n }\n\n getColspan(column: MedTableColumnConfig): number {\n return getColspanByKey(column, CONFIG_KEY_CHILDREN);\n }\n\n getRowspan(column: MedTableColumnConfig, level: number): number {\n return getRowspanByLevelAndKey(column, CONFIG_KEY_CHILDREN, this.maxDeepKeyLevel, level);\n }\n\n getCellMinWidth({minWidth}: MedTableColumnConfig): string {\n return minWidth || this.localSettings.colMinWidth;\n }\n\n getCellMaxWidth({maxWidth}: MedTableColumnConfig): string {\n return maxWidth || this.localSettings.colMaxWidth;\n }\n\n getFilterSelectOptions({key, sortKey}: CellDataConfigLocal): MedSelectOption<string>[] {\n const options = this.data\n .map(obj => get(obj, sortKey || key))\n .filter(Boolean);\n return [...new Set(options)].map(value => ({ value, label: value })); // delete duplicates\n }\n\n onFilter({filters}: PrimeOnFilterEvent<ItemType>): void {\n this.store.filters = filters;\n }\n\n onLazyLoad({first, rows, globalFilter, sortField, sortOrder, filters}: PrimeTableLazyLoadEvent) {\n const formatFilters: any[] = Object.entries(filters)\n .map(([key, data]) => ({key, value: data.value}))\n .filter(({value}) => isExist(value));\n\n const data = Object.entries({\n rows,\n sortField,\n sortOrder,\n search: globalFilter,\n page: Math.ceil(first / rows),\n filters: formatFilters,\n }).filter(([, value]) => isExist(value));\n\n this.updateTable.emit(Object.fromEntries(data) as MedUpdateTableEvent);\n }\n\n private addDoubleScrollbar() {\n // TODO should create DoubleScrollbar service\n const { nativeElement: upScrollRef } = this.upScrollRef;\n const tableRef = this.tableRef.el.nativeElement.querySelector('.p-datatable-wrapper');\n const { offsetWidth: tableWidth } = tableRef.querySelector('table');\n this.upScrollItemRef.nativeElement.style.width = `${tableWidth}px`;\n\n upScrollRef.onscroll = () => {\n if (tableRef.scrollLeft !== upScrollRef.scrollLeft)\n tableRef.scrollLeft = upScrollRef.scrollLeft;\n };\n\n tableRef.onscroll = () => {\n if (upScrollRef.scrollLeft !== tableRef.scrollLeft)\n upScrollRef.scrollLeft = tableRef.scrollLeft;\n };\n }\n}\n","<div\n #upScrollRef\n *ngIf=\"localSettings.doubleScrollbar\"\n class=\"med-table__up-scroll\"\n>\n <div #upScrollItemRef class=\"med-table__up-scroll-item\"></div>\n</div>\n\n<p-table\n #tableRef\n class=\"med-table\"\n [value]=\"data\"\n [columns]=\"config\"\n [loading]=\"loading\"\n [lazy]=\"localSettings.lazy\"\n [rows]=\"localSettings.rows\"\n [globalFilterFields]=\"filterableFields\"\n [paginator]=\"localSettings.paginator\"\n [filterDelay]=\"localSettings.filterDelay\"\n [dataKey]=\"localSettings.expandedDataKey\"\n [totalRecords]=\"localSettings.totalRecords\"\n [scrollHeight]=\"localSettings.scrollHeight\"\n [rowsPerPageOptions]=\"localSettings.rowsPerPageOptions\"\n [showCurrentPageReport]=\"localSettings.showCurrentPageReport\"\n [currentPageReportTemplate]=\"localSettings.currentPageReportTemplate\"\n (onFilter)=\"onFilter($event)\"\n (onLazyLoad)=\"onLazyLoad($event)\"\n>\n <ng-template *ngIf=\"captionTemplate || localSettings.showGlobalSearchFilter\" pTemplate=\"caption\">\n <span *ngIf=\"localSettings.showGlobalSearchFilter\" class=\"p-input-icon-left p-ml-auto mr-3\">\n <i class=\"pi pi-search\"></i>\n <input\n pInputText\n type=\"text\"\n placeholder=\"Search keyword\"\n (input)=\"tabl