import loglevel from 'loglevel' import { ComponentModel, structure } from './cykLang' import { onUnmounted, Ref, ref } from 'vue' import { componentModelParameter, NamedComponent } from './cykReact' import { FunctionData, ObjectData, PrimitiveData, Scope, Variable, Variables, XmlError } from '@cyklang/core' import { AlertException } from './cykRun' const logger = loglevel.getLogger('cykCrossView.ts') logger.setLevel('debug') /** * * @param props */ export function useCykCrossView(props: { componentArg: ComponentModel | undefined }) { try { const isLoading = ref(true) const visible = componentModelParameter(props.componentArg, "visible", true); const crossModel = new CrossModel(props.componentArg?.model?.data as ObjectData); const doLoading = async () => { try { if (props.componentArg === undefined) return; await props.componentArg.interpolateAttributes() // logger.debug(`doLoading() begin...`) isLoading.value = true await crossModel.doLoading() } catch (err) { AlertException(err) } finally { // logger.debug(`doLoading() end.`) isLoading.value = false } }; doLoading(); let namedComponent: NamedComponent if (props.componentArg?.objectData?.tag.attributes.NAME !== undefined) { namedComponent = new NamedComponent(props.componentArg.objectData.tag.attributes.NAME, (event: string) => { if (event === 'reload') { doLoading() } } ); props.componentArg.variableReact?.addNamedComponent(namedComponent) } onUnmounted(() => { if (props.componentArg?.variableReact !== undefined && namedComponent !== undefined) { props.componentArg.variableReact.removeNamedComponent(namedComponent) } }) return { isLoading, visible, crossModel } } catch (err) { AlertException(err) throw err } } /** * */ export class CrossModel { optionsObject: ObjectData queryFunct: FunctionData entityName: string columnHeaders: CrossHeaders rowHeaders: CrossHeaders rowFunct: FunctionData | undefined columnFunct: FunctionData | undefined columnLabelFunct: FunctionData | undefined rowLabelFunct: FunctionData | undefined cellLabelFunct: FunctionData | undefined cellIconFunct: FunctionData | undefined cellOnClickFunct: FunctionData | undefined crossTable: CrossTable | undefined qRows: any[] = [] qColumns: any[] = [] qKey = ref(0) /** * * @param optionsObject */ constructor(optionsObject: ObjectData) { this.optionsObject = optionsObject try { const optionsVariables = optionsObject.variables const _queryFunct = optionsVariables.getFunction('query') if (!_queryFunct) throw `query function is missing` this.queryFunct = _queryFunct const _entity = optionsVariables.getString('entity') this.entityName = _entity || "resultset" this.rowFunct = optionsVariables.getFunction("row") this.columnFunct = optionsVariables.getFunction("column") this.columnLabelFunct = optionsVariables.getFunction('column_label') this.rowLabelFunct = optionsVariables.getFunction('row_label') this.columnHeaders = new CrossHeaders(this.columnLabelFunct, this.optionsObject.scope) this.rowHeaders = new CrossHeaders(this.rowLabelFunct, this.optionsObject.scope) this.cellLabelFunct = optionsVariables.getFunction('cell_label') this.cellIconFunct = optionsVariables.getFunction('cell_icon') this.cellOnClickFunct = optionsVariables.getFunction('cell_onclick') const _columns = optionsVariables.getData('columns') if (_columns === undefined || _columns === null) throw `columns object undefined` if (!_columns.type.isObject()) throw `columns is not object` const _columns_variables = (_columns as ObjectData).variables _columns_variables.forEach((entry) => { const _value = entry.variable.getString() if (_value !== undefined) this.columnHeaders.addCode(_value) }) } catch (err) { throw new XmlError(String(err), optionsObject.tag) } } /** * * @param qColumns * @param qRows * @param qKey */ async doLoading() { try { this.qRows.length = 0 this.qColumns.length = 0 this.crossTable = await this.runquery() const qColumn: any = {} qColumn.name = "Line" qColumn.label = "" qColumn.align = 'left' qColumn.sortable = false qColumn.field = (row_id: string) => this.rowHeaders.label(row_id); this.qColumns.push(qColumn) const column_ids = this.columnHeaders.codeList() for (let ind = 0; ind < column_ids.length; ind++) { const column_id = column_ids[ind] const qColumn: any = {}; qColumn.name = column_id; qColumn.label = this.columnHeaders.label(column_id); qColumn.align = 'left' qColumn.sortable = false; qColumn.field = (row_id: string) => { return this.crossTable?.cells[row_id][column_id]?.toString() } this.qColumns.push(qColumn) } this.rowHeaders.codeList().forEach((row_id) => { this.qRows.push(row_id) }) } catch (err) { if (err instanceof XmlError) throw err logger.error(err) throw new XmlError(String(err), this.optionsObject.tag) } } /** * */ async runquery(): Promise { try { const query_result = await this.queryFunct.callFunction(new Variables(), this.optionsObject.scope) if (!query_result) throw `query function returned undefined` if (!query_result.type.isObject()) throw `query function returned ${query_result.type.name} instead of object` const dbResult = query_result as ObjectData const objEntity = dbResult.variables.getData(this.entityName) if (objEntity === undefined || objEntity === null) throw `entity ${this.entityName} not found in query result` if (!objEntity.type.isObject()) throw `entity ${this.entityName} in query result is not an object but ${objEntity.type.name}` const resultset = objEntity as ObjectData const crossTable = new CrossTable(this.rowHeaders, this.columnHeaders) for (let ind = 0; ind < resultset.variables.length(); ind++) { const namedVariable = resultset.variables.at(ind) if (!namedVariable) continue const callFunction = async (funct: FunctionData): Promise => { try { let result = '' const params = new Variables() if (funct.parametersDefinition && funct.parametersDefinition.length() > 0) { const name = funct.parametersDefinition.array[0].name params.push(name, namedVariable.variable) } const ret = await funct.callFunction(params, this.optionsObject.scope) if (ret !== undefined && ret !== null && ret.type.isPrimitive()) { result = (ret as PrimitiveData).toString() } return result } catch (err) { logger.error(err) throw err } } const _data = namedVariable.variable.data if (_data === undefined || _data === null) continue if (!_data.type.isObject()) throw `entity ${this.entityName} contains variable of type ${_data.type.name} instead of object` const record = _data as ObjectData let row_id = record.variables.getString('row_id') let column_id = record.variables.getString('column_id') if (this.rowFunct) { row_id = await callFunction(this.rowFunct) } if (this.columnFunct) { column_id = await callFunction(this.columnFunct) } if (row_id === undefined) row_id = '' if (column_id === undefined) column_id = '' let icon: string | undefined if (this.cellIconFunct) { icon = await callFunction(this.cellIconFunct) } let label: string | undefined if (this.cellLabelFunct) { label = await callFunction(this.cellLabelFunct) } await crossTable.insertRecord(row_id, column_id, record, icon, label) } return crossTable } catch (err) { throw new XmlError(String(err), this.optionsObject.tag) } } } /** * */ export class CodeLabel { code: string label: string constructor(code: string, label: string) { this.code = code this.label = label } } /** * */ export class CrossHeaders { labelFunct: FunctionData | undefined headers: CodeLabel[] scope: Scope constructor(labelFunct: FunctionData | undefined, scope: Scope) { this.labelFunct = labelFunct this.scope = scope this.headers = [] } async addCode(code: string) { try { const exists = this.headers.some(header => header.code === code); if (!exists) { // Si le code n'existe pas, générez un label et ajoutez un nouvel objet CodeLabel let label = code; if (this.labelFunct) { const params = new Variables() if (this.labelFunct.parametersDefinition && this.labelFunct.parametersDefinition.length() > 0) { const name = this.labelFunct.parametersDefinition.array[0].name params.push(name, new Variable(structure.stringDataType, new PrimitiveData(structure.stringDataType, code))) } try { const ret = await this.labelFunct.callFunction(params, this.scope); if (ret) { if (ret.type.isPrimitive()) { label = String((ret as PrimitiveData).value) } } } catch (err) { logger.error(err) if (err instanceof XmlError) throw err throw err } } this.headers.push(new CodeLabel(code, label)); this.headers.sort((header1, header2) => header1.code.localeCompare(header2.code)) } } catch (err) { logger.error(err) throw err } } codeList(): string[] { return this.headers.map(header => header.code); } label(code: string): string { const header = this.headers.find((header => header.code === code)) return header?.label || '' } } /** * */ export class CrossTable { columnHeaders: CrossHeaders rowHeaders: CrossHeaders cells: { [row_id: string]: { [column_id: string]: CrossCellContent } } = {} constructor(rowHeaders: CrossHeaders, columnHeaders: CrossHeaders) { this.rowHeaders = rowHeaders this.columnHeaders = columnHeaders } async insertRecord(row_id: string, column_id: string, record: ObjectData, icon: string | undefined, label: string | undefined) { try { // logger.debug(`insertRecord row_id: ${row_id}, column_id: ${column_id}`) // Vérifiez si la ligne existe déjà if (!this.cells[row_id]) { this.cells[row_id] = {} await this.rowHeaders.addCode(row_id) } // Vérifiez si la colonne existe déjà dans la ligne if (!this.cells[row_id][column_id]) { this.cells[row_id][column_id] = new CrossCellContent() await this.columnHeaders.addCode(column_id) } // Ajoutez l'enregistrement à la cellule this.cells[row_id][column_id].addItem(record, icon, label) } catch (err) { logger.error(err) throw err } } } export class CrossCellItem { record: ObjectData icon: string | undefined label: string | undefined constructor(record: ObjectData, icon: string | undefined, label: string | undefined) { this.record = record this.icon = icon this.label = label } } /** * */ export class CrossCellContent { items: CrossCellItem[] constructor() { this.items = [] } addItem(record: ObjectData, icon: string | undefined, label: string | undefined) { this.items.push(new CrossCellItem(record, icon, label)) } }