diff --git a/src/rootPages/Designer/editors/EditorManager.js b/src/rootPages/Designer/editors/EditorManager.js index 776d7289..7ceeeb99 100644 --- a/src/rootPages/Designer/editors/EditorManager.js +++ b/src/rootPages/Designer/editors/EditorManager.js @@ -33,6 +33,7 @@ export default function (AB) { require("./views/ABViewLayout"), require("./views/ABViewMenu"), require("./views/ABViewOrgChart"), + require("./views/ABViewOrgChartTeams"), require("./views/ABViewPage"), require("./views/ABViewPDFImporter"), require("./views/ABViewPivot"), diff --git a/src/rootPages/Designer/editors/views/ABViewOrgChartTeams.js b/src/rootPages/Designer/editors/views/ABViewOrgChartTeams.js new file mode 100644 index 00000000..5a568fb4 --- /dev/null +++ b/src/rootPages/Designer/editors/views/ABViewOrgChartTeams.js @@ -0,0 +1,53 @@ +/** + * ABViewOrgChartEditor + * The widget that displays the UI Editor Component on the screen + * when designing the UI. + */ +var myClass = null; +// {singleton} +// we will want to call this factory fn() repeatedly in our imports, +// but we only want to define 1 Class reference. + +import FABViewDefault from "./_ABViewDefault"; + +export default function (AB) { + if (!myClass) { + const ABViewDefault = FABViewDefault(AB); + // var L = UIClass.L(); + // var L = ABViewContainer.L(); + + myClass = class ABViewOrgChartTeamsEditor extends ABViewDefault { + static get key() { + return "orgchart_teams"; + } + + constructor(view, base = "interface_editor_viewOrgChartTeans") { + // base: {string} unique base id reference + + super(view, base); + + // this.component = this.view.component(); + } + + ui() { + let _ui = super.ui(); + return _ui; + } + + init(AB) { + this.AB = AB; + return super.init(AB); + } + + detatch() { + this.component?.detatch?.(); + } + + onShow() { + this.component?.onShow?.(); + } + }; + } + + return myClass; +} diff --git a/src/rootPages/Designer/properties/PropertyManager.js b/src/rootPages/Designer/properties/PropertyManager.js index 369d08e9..e08d749d 100644 --- a/src/rootPages/Designer/properties/PropertyManager.js +++ b/src/rootPages/Designer/properties/PropertyManager.js @@ -110,6 +110,7 @@ export default function (AB) { require("./views/ABViewList"), require("./views/ABViewMenu"), require("./views/ABViewOrgChart"), + require("./views/ABViewOrgChartTeams"), require("./views/ABViewPage"), require("./views/ABViewPDFImporter"), require("./views/ABViewPivot"), diff --git a/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js b/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js index c6ac0f7d..c981e262 100644 --- a/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js +++ b/src/rootPages/Designer/properties/dataFields/ABFieldConnect.js @@ -31,6 +31,12 @@ export default function (AB) { indexField: "", indexField2: "", + netsuite_one: "", + netsuiteOneLabel: "", + netsuiteOneColumn: "", + + netsuite_many: "", + connectDataPopup: "", }); } @@ -237,6 +243,37 @@ export default function (AB) { }, }, }, + { + view: "layout", + id: ids.netsuite_one, + hidden: true, + cols: [ + { + id: ids.netsuiteOneLabel, + view: "label", + label: L(" [Select object]'s field"), + width: 300, + }, + { + id: ids.netsuiteOneColumn, + name: "netsuiteOneColumn", + disallowEdit: true, + view: "richselect", + // value: FC.defaultValues().linkViaType, + // width: 200, + fillspace: true, + options: [], + on: { + // onChange: (newV, oldV) => { + // this.selectLinkViaType(newV, oldV); + // }, + onAfterRender: function () { + ABField.CYPRESS_REF(this); + }, + }, + }, + ], + }, ]); } @@ -295,6 +332,7 @@ export default function (AB) { $fieldLink2.refresh(); this.updateCustomIndex(); + this.checkNetsuiteObjects(); } show() { @@ -387,13 +425,19 @@ export default function (AB) { const options = []; // if an ABApplication is set then load in the related objects const application = this.CurrentApplication; + + // if this is a Netsuite Object, just gather other Netsuite Objs + let objFilter = () => true; + if (this.CurrentObject.isNetsuite) { + objFilter = (o) => o.isNetsuite; + } if (application) { - application.objectsIncluded().forEach((o) => { + application.objectsIncluded(objFilter).forEach((o) => { options.push({ id: o.id, value: o.label }); }); } else { // else load in all the ABObjects - this.AB.objects().forEach((o) => { + this.AB.objects(objFilter).forEach((o) => { options.push({ id: o.id, value: o.label }); }); } @@ -449,6 +493,7 @@ export default function (AB) { $field.refresh(); this.updateCustomIndex(); + this.checkNetsuiteObjects(); } selectObjectTo(newValue, oldValue) { @@ -474,6 +519,8 @@ export default function (AB) { $$(ids.link2).show(); this.updateCustomIndex(); + + this.checkNetsuiteObjects(); } updateCustomIndex() { @@ -578,6 +625,62 @@ export default function (AB) { this.checkCustomFK(); } + + async checkNetsuiteObjects() { + let ids = this.ids; + if (!this.CurrentObject.isNetsuite) { + $$(ids.netsuite_one)?.hide(); + $$(ids.netsuite_many)?.hide(); + return; + } + + let linkType = $$(ids.linkType).getValue(); + let linkViaType = $$(ids.linkViaType).getValue(); + + if (linkType == "one") { + await this.updateNetsuiteOneUI(this.CurrentObject); + $$(ids.netsuite_one)?.show(); + $$(ids.netsuite_many)?.hide(); + } else if (linkViaType == "one") { + // fill label and Drop list with object in Droplist + let objID = $$(ids.linkObject).getValue(); + let connObj = this.AB.objectByID(objID); + if (!connObj) return; + await this.updateNetsuiteOneUI(connObj); + $$(ids.netsuite_one)?.show(); + $$(ids.netsuite_many)?.hide(); + } else { + // this is many:many + $$(ids.netsuite_one)?.hide(); + $$(ids.netsuite_many)?.show(); + } + } + + async updateNetsuiteOneUI(object) { + let ids = this.ids; + + // fill label and Drop list with Current Object + $$(ids.netsuiteOneLabel).setValue( + L(" {0}'s field", [object.label]) + ); + + let result = await this.AB.Network.get({ + url: `/netsuite/table/${object.tableName}`, + params: { + credentials: JSON.stringify(object.credentials), + }, + }); + let fields = result.filter((r) => r.type == "object"); + let options = fields.map((f) => { + return { + id: f.column, + value: f.column, + }; + }); + + $$(ids.netsuiteOneColumn).define("options", options); + $$(ids.netsuiteOneColumn).refresh(); + } } return ABFieldConnectProperty; diff --git a/src/rootPages/Designer/properties/views/ABViewOrgChartTeams.js b/src/rootPages/Designer/properties/views/ABViewOrgChartTeams.js new file mode 100644 index 00000000..e2f1acbe --- /dev/null +++ b/src/rootPages/Designer/properties/views/ABViewOrgChartTeams.js @@ -0,0 +1,1591 @@ +/* + * ABViewChartTeams + * A Property manager for our ABViewChartTeams definitions + */ + +import FABView from "./ABView"; + +export default function (AB) { + const BASE_ID = "properties_abview_org_chart_teams"; + + const ABView = FABView(AB); + const uiConfig = AB.Config.uiSettings(); + const L = ABView.L(); + + class ABViewOrgChartTeamsProperty extends ABView { + constructor() { + super(BASE_ID, { + datacollectionID: "", + strategyCode: "", + subStrategy: "", + teamInactive: "", + teamCanInactivate: "", + teamLink: "", + teamName: "", + teamStrategy: "", + topTeam: "", + fields: "", + direction: "", + depth: "", + draggable: "", + dropContentToCreate: "", + pan: "", + zoom: "", + height: "", + export: "", + exportFilename: "", + groupByField: "", + showGroupTitle: "", + editContentFieldsToCreateNew: "", + setEditableContentFields: "", + contentField: "", + contentFieldFilter: "", + contentFieldDateStart: "", + contentFieldDateEnd: "", + contentFieldFilterButton: "", + contentGroupByField: "", + contentDisplayedFields: "", + contentDisplayedFieldsAdd: "", + contentDisplayedFieldFilters: "", + contentDisplayedFieldFiltersSet: "", + contentDisplayedFieldTypes: "", + contentDisplayedFieldTypesSet: "", + contentDisplayedFieldMappingData: "", + contentDisplayedFieldMappingDataSet: "", + showDataPanel: "", + dataPanelDCs: "", + dataPanelDCsAdd: "", + strategyColorPopup: "", + strategyColorForm: "", + entityDatacollection: "", + }); + this.AB = AB; + const contentFieldFilter = (this.contentFieldFilter = + AB.filterComplexNew(this.ids.contentFieldFilter)); + contentFieldFilter.on("save", () => { + if ( + !contentFieldFilter.isConditionComplete( + contentFieldFilter.getValue() + ) + ) + contentFieldFilter.setValue({ glue: "and", rules: [] }); + this.onChange(); + }); + } + + static get key() { + return "orgchart_teams"; + } + + _uiDataPanelDC(labelValue = "", dcID = "") { + const self = this; + const ids = self.ids; + const $dataPanelDCs = $$(ids.dataPanelDCs); + const validOBJIDs = this.CurrentView.datacollection.datasource + .fieldByID($$(ids.contentField).getValue()) + .datasourceLink.connectFields( + (connectField) => connectField.linkType() === "one" + ) + .map((connectField) => connectField.datasourceLink.id); + const dcs = this.AB.datacollections( + (dc) => validOBJIDs.indexOf(dc.datasource.id) > -1 + ); + const dcOptions = dcs.map((dc) => ({ + id: dc.id, + value: dc.label, + dc, + })); + const getUILabel = (dcID, elementIndex) => ({ + view: "text", + name: `${elementIndex}.${dcID}`, + // label: L("Name"), + // labelWidth: uiConfig.labelWidthMedium, + on: { + onChange: () => { + this.onChange(); + }, + onViewShow() { + this.setValue(labelValue); + }, + }, + }); + return { + cols: [ + { + view: "richselect", + label: `${L("Panel")} ${ + $dataPanelDCs.getChildViews().length + 1 + }`, + labelWidth: uiConfig.labelWidthMedium, + options: dcOptions, + on: { + onChange(newValue) { + const $parentView = this.getParentView(); + const sameLevelViews = $parentView.getChildViews(); + if ($parentView.getChildViews().length === 3) + $parentView.removeView(sameLevelViews[1].config.id); + $parentView.addView( + getUILabel( + newValue, + $dataPanelDCs + .getChildViews() + .findIndex( + ($dataPanelDCsChild) => + $dataPanelDCsChild === $parentView + ) + ), + 1 + ); + }, + onViewShow() { + if (dcID == null || dcID === "") return; + this.setValue(dcID); + }, + }, + }, + { + view: "button", + css: "webix_danger", + type: "icon", + icon: "wxi-close", + width: uiConfig.buttonWidthExtraSmall, + click() { + self.deleteDataPanelDC(this.getParentView().config.id); + self.onChange(); + }, + }, + ], + }; + } + + _uiContentDisplayedField(fieldID = "", obj, atDisplay) { + const self = this; + const ids = self.ids; + const datasource = this.CurrentView.datacollection.datasource; + const datasourceID = datasource.id; + const parentObj = datasource.fieldByID( + $$(ids.contentField).getValue() + ).datasourceLink; + const parentObjID = parentObj.id; + const objID = obj?.id || parentObjID; + const $contentDisplayedFields = $$(ids.contentDisplayedFields); + const $contentDisplayedFieldTypes = $$(ids.contentDisplayedFieldTypes); + const $contentDisplayedFieldMappingData = $$( + ids.contentDisplayedFieldMappingData + ); + const $contentDisplayedFieldFilters = $$( + ids.contentDisplayedFieldFilters + ); + const filterFields = (f) => { + const linkedObjID = f.datasourceLink?.id; + return linkedObjID !== datasourceID && linkedObjID !== parentObjID; + }; + const getOnSelectChangeFn = + (currentObj, currentAtDisplay) => (newValue) => { + const field = currentObj.fieldByID(newValue); + if (field.key === "connectObject") { + $contentDisplayedFields.addView( + this._uiContentDisplayedField( + "", + field.datasourceLink, + currentAtDisplay + ) + ); + } + this.populateContentDisplayedFields( + $contentDisplayedFields.getValues(), + $contentDisplayedFieldTypes.getValues(), + $contentDisplayedFieldMappingData.getValues(), + $contentDisplayedFieldFilters.getValues() + ); + this.onChange(); + }; + if (objID === parentObjID) { + const rootAtDisplay = Object.keys( + $contentDisplayedFields.elements + ).filter((key) => key.includes(objID)).length; + return { + cols: [ + { + view: "richselect", + name: `${rootAtDisplay}.${parentObjID}`, + label: `${L("Display")} ${rootAtDisplay + 1}`, + labelWidth: uiConfig.labelWidthMedium, + options: + parentObj.fields(filterFields).map(fieldToOption) || [], + value: fieldID, + on: { + onChange: getOnSelectChangeFn(parentObj, rootAtDisplay), + }, + }, + { + view: "button", + css: "webix_danger", + type: "icon", + icon: "wxi-close", + width: uiConfig.buttonWidthExtraSmall, + click() { + self.deleteContentDisplayedField( + this.getParentView().getChildViews()[0].config.id + ); + self.onChange(); + }, + }, + ], + }; + } + return { + cols: [ + { + view: "richselect", + name: `${atDisplay}.${objID}`, + label: "->", + labelWidth: uiConfig.labelWidthMedium, + options: obj.fields(filterFields).map(fieldToOption) || [], + value: fieldID, + on: { + onChange: getOnSelectChangeFn(obj, atDisplay), + }, + }, + { + view: "button", + css: "webix_danger", + type: "icon", + icon: "wxi-close", + width: uiConfig.buttonWidthExtraSmall, + click() { + self.deleteContentDisplayedField( + this.getParentView().getChildViews()[0].config.id + ); + self.onChange(); + }, + }, + ], + }; + } + + ui() { + const ids = this.ids; + const contentFieldFilter = this.contentFieldFilter; + return super.ui([ + { + id: ids.datacollectionID, + name: "datacollectionID", + view: "richselect", + label: L("Team Data"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { + onChange: (value) => { + this.CurrentView.settings.datacollectionID = value; + const obj = this.CurrentView?.datacollection?.datasource; + this.populateTeamFieldOptions(obj); + this.onChange(); + }, + }, + }, + { + id: ids.teamLink, + view: "richselect", + label: L("Team Link"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { onChange: () => this.onChange() }, + }, + { + id: ids.teamName, + view: "richselect", + label: L("Team Name"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { onChange: () => this.onChange() }, + }, + { + id: ids.topTeam, + view: "richselect", + label: L("Top Team"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { onChange: () => this.onChange() }, + }, + { + id: ids.teamInactive, + view: "richselect", + label: L("Team Inactive"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { onChange: () => this.onChange() }, + }, + { + id: ids.teamCanInactivate, + view: "richselect", + label: L("Can Inactivate"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { onChange: () => this.onChange() }, + }, + { + cols: [ + { + view: "label", + label: L("Content Field"), + width: uiConfig.labelWidthLarge, + }, + { + id: ids.contentField, + name: "contentField", + view: "richselect", + options: [], + on: { + onChange: (newValue) => { + const $editContentFieldsToCreateNew = $$( + ids.editContentFieldsToCreateNew + ); + const $setEditableContentFields = $$( + ids.setEditableContentFields + ); + const $contentDisplayedFieldsAdd = $$( + ids.contentDisplayedFieldsAdd + ); + const $contentFieldFilterButton = $$( + ids.contentFieldFilterButton + ); + const $contentGroupByField = $$( + ids.contentGroupByField + ); + const $contentFieldDateStart = $$( + ids.contentFieldDateStart + ); + const $contentFieldDateEnd = $$( + ids.contentFieldDateEnd + ); + const $showGroupTitle = $$(ids.showGroupTitle); + contentFieldFilter.init(); + contentFieldFilter.setValue({ + glue: "and", + rules: [], + }); + if (newValue != null && newValue !== "") { + const contentObj = + this.CurrentView.datacollection.datasource.fieldByID( + newValue + ).datasourceLink; + const contentObjFields = contentObj.fields(); + const contentDateFiels = contentObjFields.filter( + (field) => + field.key === "date" || + field.key === "datetime" + ); + $contentFieldDateStart.define( + "options", + contentDateFiels.map(fieldToOption) + ); + $contentFieldDateEnd.define( + "options", + contentDateFiels.map(fieldToOption) + ); + const contentObjMappedFields = + // contentObjFields.map(fieldToOption) *** (Guy) this makes my chrome browser crash (Only multi-selection) it is because { field: f } *** + contentObjFields.map((contentObjField) => ({ + id: contentObjField.id, + value: contentObjField.label, + })); + $editContentFieldsToCreateNew.define( + "options", + contentObjMappedFields + ); + $setEditableContentFields.define( + "options", + contentObjMappedFields + ); + contentFieldFilter.fieldsLoad(contentObjFields); + $contentGroupByField.define("options", [ + { id: "", value: "", $empty: true }, + ...contentObjFields + .filter( + (f) => f.key === "connectObject" //&& + // f.settings.isMultiple === 0 + ) + .map(fieldToOption), + ]); + $editContentFieldsToCreateNew.enable(); + $contentFieldFilterButton.enable(); + $contentDisplayedFieldsAdd.show(); + $contentFieldDateStart.show(); + $contentFieldDateEnd.show(); + $contentGroupByField.show(); + $showGroupTitle.show(); + } else { + $editContentFieldsToCreateNew.define( + "options", + [] + ); + contentFieldFilter.fieldsLoad([]); + $contentGroupByField.define("options", []); + $editContentFieldsToCreateNew.enable(); + $contentFieldFilterButton.disable(); + $contentDisplayedFieldsAdd.hide(); + $contentFieldDateStart.hide(); + $contentFieldDateEnd.hide(); + $contentGroupByField.hide(); + $showGroupTitle.hide(); + } + $editContentFieldsToCreateNew.setValue([]); + $showGroupTitle.setValue(0); + $contentGroupByField.setValue(""); + this.populateContentDisplayedFields(); + this.onChange(); + }, + }, + }, + { + id: ids.contentFieldFilterButton, + view: "button", + type: "icon", + icon: "fa fa-filter", + css: "webix_primary", + disabled: true, + width: uiConfig.buttonWidthExtraSmall, + click() { + contentFieldFilter.popUp(this.$view, null, { + pos: "top", + }); + }, + }, + ], + }, + { + id: ids.contentFieldDateStart, + name: "contentFieldDateStart", + label: L("Date Start"), + labelWidth: uiConfig.labelWidthLarge, + view: "richselect", + options: [], + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + id: ids.contentFieldDateEnd, + name: "contentFieldDateEnd", + label: L("Date End"), + labelWidth: uiConfig.labelWidthLarge, + view: "richselect", + options: [], + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + id: ids.contentGroupByField, + hidden: true, + view: "richselect", + label: L("Content Group By Field"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { + onChange: (/*newValue*/) => { + this.onChange(); + }, + }, + }, + { + id: ids.showGroupTitle, + hidden: true, + name: "showGroupTitle", + view: "checkbox", + label: L("Show Group Title"), + labelWidth: uiConfig.labelWidthLarge, + value: 0, + on: { + onChange: (/*newValue*/) => { + this.onChange(); + }, + }, + }, + { + rows: [ + { + view: "label", + label: L( + "Force the creation of a new row of data by editing the content fields" + ), + }, + { + id: ids.editContentFieldsToCreateNew, + view: "multicombo", + value: [], + options: [], + placeholder: L( + "Choose the content fields to create a new entry through editing" + ), + labelAlign: "left", + stringResult: false /* returns data as an array of [id] */, + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + view: "label", + label: L("Set the editable content fields"), + }, + { + id: ids.setEditableContentFields, + view: "multicombo", + value: [], + options: [], + placeholder: L("Choose the editable content fields"), + labelAlign: "left", + stringResult: false /* returns data as an array of [id] */, + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + ], + }, + { + id: ids.contentDisplayedFieldsAdd, + hidden: true, + cols: [ + { + view: "label", + label: L("Content Displayed Fields"), + }, + { + view: "button", + type: "icon", + icon: "fa fa-plus", + css: "webix_primary", + width: uiConfig.buttonWidthExtraSmall, + click: () => { + const $contentDisplayedFields = $$( + ids.contentDisplayedFields + ); + if (!$contentDisplayedFields.isVisible()) + $contentDisplayedFields.show(); + $contentDisplayedFields.addView( + this._uiContentDisplayedField() + ); + }, + }, + ], + }, + { + id: ids.contentDisplayedFields, + view: "form", + hidden: true, + elements: [], + }, + { + id: ids.contentDisplayedFieldTypesSet, + hidden: true, + rows: [ + { + view: "label", + label: L( + "Set content active displays and displayed types" + ), + }, + { + id: ids.contentDisplayedFieldTypes, + view: "form", + elements: [], + }, + ], + }, + { + id: ids.contentDisplayedFieldMappingDataSet, + hidden: true, + rows: [ + { + view: "label", + label: L("Set content mapping displayed data"), + }, + { + id: ids.contentDisplayedFieldMappingData, + view: "form", + elements: [], + }, + ], + }, + { + id: ids.contentDisplayedFieldFiltersSet, + hidden: true, + rows: [ + { + view: "label", + label: L("Set content displayed filters by field"), + }, + { + id: ids.contentDisplayedFieldFilters, + view: "form", + elements: [], + }, + ], + }, + { + id: ids.showDataPanel, + name: "showDataPanel", + view: "checkbox", + label: L("Show Data Panel"), + labelWidth: uiConfig.labelWidthLarge, + value: 0, + on: { + onChange: (newValue) => { + const $dataPanelDCsAdd = $$(ids.dataPanelDCsAdd); + if (newValue === 1) $dataPanelDCsAdd.show(); + else $dataPanelDCsAdd.hide(); + this.populateDataPanelDCs({}); + this.onChange(); + }, + }, + }, + { + id: ids.dataPanelDCsAdd, + hidden: true, + cols: [ + { + view: "label", + label: L("Data Panel DCs"), + }, + { + view: "button", + type: "icon", + icon: "fa fa-plus", + css: "webix_primary", + width: uiConfig.buttonWidthExtraSmall, + click: () => { + const $dataPanelDCs = $$(ids.dataPanelDCs); + if (!$dataPanelDCs.isVisible()) $dataPanelDCs.show(); + $dataPanelDCs.addView(this._uiDataPanelDC()); + }, + }, + ], + }, + { + id: ids.dataPanelDCs, + view: "form", + hidden: true, + elements: [], + }, + { + id: ids.teamStrategy, + view: "richselect", + label: L("Strategy"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { + onChange: (value) => { + this.populateStrategyOptions(value); + this.onChange(); + }, + }, + }, + { + cols: [ + { + id: ids.strategyCode, + view: "richselect", + label: L("Strategy Code"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { + onChange: () => { + this.onChange(); + $$(this.ids.strategyColorPopup)?.close(); + }, + }, + }, + { + view: "icon", + icon: "fa fa-paint-brush", + allign: "right", + click: () => this.strategyColorPopup(), + }, + ], + }, + { + id: ids.subStrategy, + view: "richselect", + label: L("Sub Strategy"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { onChange: () => this.onChange() }, + }, + { + id: ids.draggable, + name: "draggable", + view: "checkbox", + label: L("Drag & Drop"), + labelWidth: uiConfig.labelWidthLarge, + value: 0, + on: { + onChange: (newValue) => { + const $dropContentToCreate = $$(ids.dropContentToCreate); + if (newValue === 0) { + $dropContentToCreate.setValue(0); + $dropContentToCreate.hide(); + } else $dropContentToCreate.show(); + this.onChange(); + }, + }, + }, + { + id: ids.dropContentToCreate, + name: "dropContentToCreate", + view: "checkbox", + label: L("Drop content to create"), + labelWidth: uiConfig.labelWidthLarge, + hidden: true, + value: 0, + on: { onChange: () => this.onChange() }, + }, + { + id: ids.direction, + name: "direction", + view: "richselect", + label: L("Direction"), + labelWidth: uiConfig.labelWidthLarge, + options: [ + { id: "t2b", value: L("Top to Bottom") }, + { id: "b2t", value: L("Bottom to Top") }, + { id: "l2r", value: L("Left to Right") }, + { id: "r2l", value: L("Right to Left") }, + ], + on: { onChange: () => this.onChange() }, + }, + { + id: ids.entityDatacollection, + name: "entityDatacollection", + view: "richselect", + label: L("Entity"), + labelWidth: uiConfig.labelWidthLarge, + options: [], + on: { + onChange: () => this.onChange(), + }, + }, + { + id: ids.depth, + name: "depth", + hidden: true, // NOTE: use choose Connect Fields option + view: "counter", + label: L("Depth"), + labelWidth: uiConfig.labelWidthLarge, + value: 0, + on: { onChange: () => this.onChange() }, + }, + { + hidden: true, // NOTE: does not support + id: ids.pan, + name: "pan", + view: "checkbox", + label: L("Pan"), + labelWidth: uiConfig.labelWidthLarge, + value: 0, + on: { onChange: () => this.onChange() }, + }, + { + hidden: true, // NOTE: does not support + id: ids.zoom, + name: "zoom", + view: "checkbox", + label: L("Zoom"), + labelWidth: uiConfig.labelWidthLarge, + value: 0, + on: { onChange: () => this.onChange() }, + }, + { + id: ids.height, + view: "counter", + name: "height", + label: L("Height"), + labelWidth: uiConfig.labelWidthLarge, + on: { onChange: () => this.onChange() }, + }, + { + hidden: true, // NOTE: does not support + view: "fieldset", + label: L("Export"), + body: { + view: "layout", + borderless: true, + rows: [ + { + id: ids.export, + name: "export", + view: "checkbox", + label: L("Is Exportable"), + labelWidth: uiConfig.labelWidthLarge, + value: 0, + on: { + onChange: () => { + this.onChange(); + }, + }, + }, + { + id: ids.exportFilename, + view: "text", + name: "exportFilename", + label: L("File name"), + placeholder: L("Enter file name"), + labelWidth: uiConfig.labelWidthLarge, + }, + ], + }, + }, + ]); + } + + async init(AB) { + this.AB = AB; + await super.init(AB); + webix.extend($$(this.ids.component), webix.ProgressBar); + this.contentFieldFilter.queriesLoad( + this.CurrentApplication?.queriesIncluded() + ); + } + + deleteContentDisplayedField(id) { + const ids = this.ids; + const $contentDisplayedFields = $$(ids.contentDisplayedFields); + const $elements = $contentDisplayedFields.elements; + const $richselect = $$(id); + const deletedElementKey = $richselect.config.name; + if ( + deletedElementKey.includes( + this.CurrentView.datacollection.datasource.fieldByID( + $$(ids.contentField).getValue() + ).datasourceLink.id + ) + ) { + const deletedAtDisplay = deletedElementKey.split(".")[0]; + for (const key in $elements) { + if (!key.includes(`${deletedAtDisplay}.`)) continue; + $contentDisplayedFields.removeView( + $elements[key].getParentView().config.id + ); + } + } else + $contentDisplayedFields.removeView( + $richselect.getParentView().config.id + ); + this.populateContentDisplayedFields( + $contentDisplayedFields.getValues() + ); + } + + deleteDataPanelDC(id) { + const $dataPanelDCs = $$(this.ids.dataPanelDCs); + $dataPanelDCs.removeView(id); + this.populateDataPanelDCs($dataPanelDCs.getValues()); + } + + populate(view) { + super.populate(view); + const ids = this.ids; + const $component = $$(ids.component); + const defaultValues = this.defaultValues(); + const values = Object.assign( + $component.getValues(), + Object.assign(defaultValues, view.settings) + ); + // const $fieldList = $$(ids.fields); + // $fieldList.clearAll(); + this.populateDatacollection(values.datacollectionId); + const teamObj = this.CurrentView?.datacollection?.datasource; + if (teamObj) { + this.populateTeamFieldOptions(teamObj); + [ + "teamCanInactivate", + "teamInactive", + "teamLink", + "teamName", + "teamStrategy", + "topTeam", + "contentField", + "contentGroupByField", + "editContentFieldsToCreateNew", + "setEditableContentFields", + "showGroupTitle", + "showDataPanel", + ].forEach((f) => $$(ids[f]).setValue(values[f])); + this.contentFieldFilter.setValue( + JSON.parse(values.contentFieldFilter) + ); + this.populateContentDisplayedFields( + values.contentDisplayedFields, + values.contentDisplayedFieldTypes, + values.contentDisplayedFieldMappingData, + values.contentDisplayedFieldFilters + ); + this.populateDataPanelDCs(values.dataPanelDCs); + if (values.teamStrategy) { + this.populateStrategyOptions(values.teamStrategy); + $$(ids.strategyCode).setValue(values.strategyCode); + $$(ids.subStrategy).setValue(values.subStrategy); + } + } + $component.setValues(values); + } + + populateDatacollection(datacollectionId) { + const $dataCollection = $$(this.ids.datacollectionID); + + // Pull data collections to options + const dcOptions = this.CurrentView.application + .datacollectionsIncluded() + .map((d) => { + return { id: d.id, value: d.label }; + }); + $dataCollection.define("options", dcOptions); + $dataCollection.define("value", datacollectionId); + $dataCollection.refresh(); + $$(this.ids.entityDatacollection).define("options", dcOptions); + $$(this.ids.entityDatacollection).refresh(); + } + + refreshValueFieldOptions(fieldValues = []) { + const ids = this.ids; + const view = this.CurrentView; + const $fieldList = $$(ids.fields); + + $fieldList.clearAll(); + + // Populate 1:M fields option of the root object + this.populateSubValueFieldOptions(view.datacollection?.datasource); + + // Populate sub 1:M fields option of each fields + fieldValues.forEach((fId) => { + if (!fId) return; + + const $fieldItem = $fieldList.getItem(fId); + if ($fieldItem) { + const abField = $fieldItem.field; + this.populateSubValueFieldOptions(abField.datasourceLink, fId); + } + }); + + // Set check items + $fieldList.blockEvent(); + fieldValues.forEach((fId) => { + if ($fieldList.exists(fId)) $fieldList.checkItem(fId); + }); + $fieldList.unblockEvent(); + } + + populateTeamFieldOptions(object) { + const view = this.CurrentView; + const ids = this.ids; + const m2oFields = view.getValueFields(object).map(fieldToOption); + const o2mFields = + object.connectFields( + (f) => f.linkType() == "one" && f.linkViaType() == "many" + ) ?? []; + $$(ids.teamStrategy).define("options", o2mFields.map(fieldToOption)); + $$(ids.teamLink).define("options", m2oFields); + const textFields = object + ?.fields((f) => f.key === "string") + .map(fieldToOption); + $$(ids.teamName).define("options", textFields); + const booleanFields = object + ?.fields((f) => f.key === "boolean") + .map(fieldToOption); + + // Add an empty option as this is an optional setting. + booleanFields.unshift({ id: "", value: "", $empty: true }); + $$(ids.topTeam).define("options", booleanFields); + $$(ids.teamInactive).define("options", booleanFields); + $$(ids.teamCanInactivate).define("options", booleanFields); + $$(ids.contentField).define("options", [ + { id: "", value: "", $empty: true }, + ...m2oFields, + ]); + } + + populateContentDisplayedFields( + values = {}, + types = {}, + mappingDataValues = {}, + filters = {} + ) { + const self = this; + const webix = this.AB.Webix; + const ids = this.ids; + const $contentDisplayedFields = $$(ids.contentDisplayedFields); + const contentDisplayedFieldsElements = + $contentDisplayedFields.elements; + for (const key in contentDisplayedFieldsElements) + $contentDisplayedFields.removeView( + contentDisplayedFieldsElements[key].getParentView().config.id + ); + const $contentDisplayedFieldTypes = $$(ids.contentDisplayedFieldTypes); + const contentDisplayedFieldTypesElements = + $contentDisplayedFieldTypes.elements; + const $contentDisplayedFieldTypesSet = $$( + ids.contentDisplayedFieldTypesSet + ); + for (const key in contentDisplayedFieldTypesElements) + $contentDisplayedFieldTypes.removeView( + contentDisplayedFieldTypesElements[key].getParentView().config.id + ); + const $contentDisplayedFieldMappingData = $$( + ids.contentDisplayedFieldMappingData + ); + const contentDisplayedFieldMappingDataElements = + $contentDisplayedFieldMappingData.elements; + const $contentDisplayedFieldMappingDataSet = $$( + ids.contentDisplayedFieldMappingDataSet + ); + const $contentDisplayedFieldFilters = $$( + ids.contentDisplayedFieldFilters + ); + for (const key in contentDisplayedFieldMappingDataElements) + $contentDisplayedFieldMappingData.removeView( + contentDisplayedFieldMappingDataElements[key].getParentView() + .config.id + ); + const contentDisplayedFieldFiltersElements = + $contentDisplayedFieldFilters.elements; + const $contentDisplayedFieldFiltersSet = $$( + ids.contentDisplayedFieldFiltersSet + ); + for (const key in contentDisplayedFieldFiltersElements) + $contentDisplayedFieldFilters.removeView( + contentDisplayedFieldFiltersElements[key].getParentView().config + .id + ); + $contentDisplayedFieldTypesSet.hide(); + $contentDisplayedFieldMappingDataSet.hide(); + $contentDisplayedFieldFiltersSet.hide(); + const keys = Object.keys(values); + if (keys.length === 0) { + $contentDisplayedFields.hide(); + return; + } + const obj = this.CurrentView.datacollection.datasource.fieldByID( + $$(ids.contentField).getValue() + )?.datasourceLink; + if (obj == null) { + $contentDisplayedFields.hide(); + $$(ids.contentDisplayedFieldsAdd).hide(); + return; + } + const typeKeys = Object.keys(types); + const filterKeys = Object.keys(filters); + const objID = obj.id; + const parentKeys = []; + const childKeys = []; + const createOptionsView = (key, field) => { + const optionPrefix = `${key}.${field.id}`; + const fieldLabel = field.label; + const typeKeyIndex = typeKeys.findIndex( + (typeKey) => typeKey.indexOf(optionPrefix) > -1 + ); + const typeSwitchValue = parseInt( + typeKeys + .find((typeKey) => typeKey.indexOf(optionPrefix) > -1) + ?.split(".")[3] + ); + $contentDisplayedFieldTypes.addView({ + cols: [ + { + view: "switch", + label: fieldLabel, + labelWidth: uiConfig.labelWidthMedium, + value: isNaN(typeSwitchValue) ? 1 : typeSwitchValue, + on: { + onChange: (newValue, oldValue) => { + const oldTypes = + $contentDisplayedFieldTypes.getValues(); + const oldTypeEntries = Object.entries(oldTypes); + const newTypes = {}; + const oldTypePrefix = `${optionPrefix}.${oldValue}`; + for (const [key, value] of oldTypeEntries) + if (key.indexOf(oldTypePrefix) > -1) + newTypes[`${optionPrefix}.${newValue}`] = + value; + else newTypes[key] = value; + this.populateContentDisplayedFields( + $contentDisplayedFields.getValues(), + newTypes, + $contentDisplayedFieldMappingData.getValues(), + $contentDisplayedFieldFilters.getValues() + ); + this.onChange(); + }, + onViewShow() { + this.getParentView().addView({ + view: "richselect", + options: [ + { id: "icon", value: "Icon" }, + { id: "image", value: "Image" }, + { id: "svg", value: "SVG" }, + { id: "text", value: "Text" }, + ], + name: `${optionPrefix}.${this.getValue()}`, + value: + (typeKeyIndex > -1 && + types[typeKeys[typeKeyIndex]]) || + "text", + on: { + onChange: () => { + self.onChange(); + }, + }, + }); + }, + }, + }, + ], + }); + const mappingDataValue = mappingDataValues[optionPrefix]; + $contentDisplayedFieldMappingData.addView({ + cols: [ + { + view: "label", + label: fieldLabel, + width: uiConfig.labelWidthMedium, + }, + { + view: "button", + label: "Set", + width: uiConfig.buttonWidthExtraSmall, + click: () => { + const getValueViewUI = (key = "", value = "") => ({ + cols: [ + { + view: "text", + placeholder: L("The value need to map."), + value: key, + }, + { + view: "label", + label: L("to"), + align: "center", + width: uiConfig.labelWidthSmall, + }, + { + view: "text", + placeholder: (() => { + switch (value) { + case "icon": + return L("Icon class text."); + case "image": + case "svg": + return L( + "Image url or Base64 (ex. data:image/png;base64,AAABBBCCC) url." + ); + default: + return L("New text."); + } + })(), + value, + }, + { + view: "button", + css: "webix_danger", + type: "icon", + icon: "wxi-close", + width: uiConfig.buttonWidthExtraSmall, + click() { + const $childView = this.getParentView(); + $childView + .getParentView() + .removeView($childView.config.id); + }, + }, + ], + }); + const $popup = webix.ui({ + view: "window", + close: true, + title: L("Map Data"), + position: "center", + body: { + view: "form", + elements: [ + { + view: "button", + label: L("Add a value"), + click() { + this.getParentView() + .getChildViews()[1] + .addView(getValueViewUI()); + }, + }, + { + rows: [], + }, + { + view: "button", + label: L("Apply"), + click() { + const $valueView = + this.getParentView().getChildViews()[1]; + const mapingValues = {}; + const valueChildViews = + $valueView.getChildViews(); + for (const $valueChildView of valueChildViews) { + const valueChildViewElements = + $valueChildView.getChildViews(); + mapingValues[ + valueChildViewElements[0].getValue() + ] = + valueChildViewElements[2].getValue(); + } + $contentDisplayedFieldMappingData.elements[ + optionPrefix + ]?.setValue( + JSON.stringify(mapingValues) + ); + $popup.hide(); + self.onChange(); + }, + }, + ], + }, + on: { + onHide() { + this.destructor(); + }, + }, + }); + try { + const $valueView = $popup + .getChildViews()[1] + .getChildViews()[1]; + const mappingDataObj = JSON.parse(mappingDataValue); + for (const key in mappingDataObj) + $valueView.addView( + getValueViewUI(key, mappingDataObj[key]) + ); + } catch {} + $popup.show(); + }, + }, + { + view: "text", + name: optionPrefix, + disabled: true, + value: mappingDataValue || JSON.stringify({}), + }, + ], + }); + const filterCheckboxValue = parseInt( + filterKeys + .find((filterKey) => filterKey.indexOf(optionPrefix) > -1) + ?.split(".")[3] + ); + $contentDisplayedFieldFilters.addView({ + cols: [ + { + view: "checkbox", + label: fieldLabel, + labelWidth: uiConfig.labelWidthMedium, + value: isNaN(filterCheckboxValue) + ? 0 + : filterCheckboxValue, + on: { + onChange: (newValue, oldValue) => { + const oldFilters = + $contentDisplayedFieldFilters.getValues(); + const oldFilterEntries = Object.entries(oldFilters); + const newFilters = {}; + const oldFilterKey = `${optionPrefix}.${oldValue}`; + for (const [key, value] of oldFilterEntries) + if (key === oldFilterKey) + newFilters[`${optionPrefix}.${newValue}`] = ""; + else newFilters[key] = value; + this.populateContentDisplayedFields( + $contentDisplayedFields.getValues(), + $contentDisplayedFieldTypes.getValues(), + $contentDisplayedFieldMappingData.getValues(), + newFilters + ); + this.onChange(); + }, + onViewShow() { + const currentFilterCheckboxValue = this.getValue(); + const filterKey = `${optionPrefix}.${currentFilterCheckboxValue}`; + this.getParentView().addView({ + view: "text", + placeholder: L("Add the new field label."), + name: filterKey, + value: filters[filterKey] || fieldLabel, + disabled: + currentFilterCheckboxValue === 1 + ? false + : true, + on: { + onChange: () => { + self.onChange(); + }, + }, + }); + }, + }, + }, + ], + }); + }; + while (keys.length > 0) { + const key = keys.pop(); + (key.includes(objID) && parentKeys.push(key)) || + childKeys.push(key); + } + while (parentKeys.length > 0) { + const parentKey = parentKeys.pop(); + const parentFieldID = values[parentKey] ?? ""; + $contentDisplayedFields.addView( + this._uiContentDisplayedField(parentFieldID) + ); + const parentField = obj.fieldByID(parentFieldID); + if (parentField == null) continue; + switch (parentField.key) { + case "connectObject": + case "user": + break; + default: + createOptionsView(parentKey, parentField); + continue; + } + const currentAtDisplay = + Object.keys($contentDisplayedFields.getValues()).filter( + (currentKey) => currentKey.includes(objID) + ).length - 1; + while ( + childKeys.findIndex((childKey) => + childKey.includes(`${parentKey.split(".")[0]}.`) + ) > -1 + ) { + const childKey = childKeys.pop(); + const childObj = this.AB.objectByID(childKey.split(".")[1]); + const childFieldID = values[childKey] ?? ""; + $contentDisplayedFields.addView( + this._uiContentDisplayedField( + childFieldID, + childObj, + currentAtDisplay + ) + ); + const childField = childObj.fieldByID(childFieldID); + if (childField == null) continue; + switch (childField.key) { + case "connectObject": + case "user": + break; + default: + createOptionsView(childKey, childField); + continue; + } + } + } + $contentDisplayedFields.show(); + $contentDisplayedFieldTypesSet.show(); + $contentDisplayedFieldMappingDataSet.show(); + $contentDisplayedFieldFiltersSet.show(); + } + + populateDataPanelDCs(values) { + const ids = this.ids; + const $dataPanelDCs = $$(ids.dataPanelDCs); + const dataPanelDCsChidren = $dataPanelDCs.getChildViews(); + while (dataPanelDCsChidren.length > 0) + $dataPanelDCs.removeView(dataPanelDCsChidren[0].config.id); + $dataPanelDCs.hide(); + const contentFieldValue = $$(ids.contentField).getValue(); + const keys = Object.keys(values); + if ( + contentFieldValue == null || + contentFieldValue === "" || + keys.length === 0 + ) + return; + while (keys.length > 0) { + const key = keys.shift(); + $dataPanelDCs.addView( + this._uiDataPanelDC(values[key] ?? "", key.split(".")[1] ?? "") + ); + } + $dataPanelDCs.show(); + } + + populateStrategyOptions(fieldID) { + const strategyObj = this.AB.objectByID( + this.AB.definitionByID(fieldID).settings.linkObject + ); + const listFields = strategyObj + .fields((f) => f.key === "connectObject") + .map(fieldToOption); + $$(this.ids.strategyCode).define("options", listFields); + $$(this.ids.subStrategy).define("options", listFields); + } + + async strategyColorPopup() { + const codeFieldID = $$(this.ids.strategyCode).getValue(); + if (!codeFieldID) return; + + let $popup = $$(this.ids.strategyColorPopup); + + if (!$popup) { + const values = this.CurrentView.settings.strategyColors ?? {}; + const link = + this.AB.definitionByID(codeFieldID).settings.linkObject; + const strategies = await this.AB.objectByID(link).model().findAll(); + const strategyTypes = strategies.data.map((strategy) => { + return { + view: "colorpicker", + label: strategy.name, + name: strategy.id, + value: values[strategy.id] ?? "#111111", + suggest: { + type: "colorselect", + body: { + button: true, + }, + }, + }; + }); + + $popup = this.AB.Webix.ui({ + view: "window", + id: this.ids.strategyColorPopup, + close: true, + title: L("Set Colors"), + position: "center", + body: { + view: "form", + id: this.ids.strategyColorForm, + elements: [ + ...strategyTypes, + { + view: "button", + label: L("Apply"), + click: () => { + this.onChange(); + $$(this.ids.strategyColorPopup).hide(); + }, + }, + ], + }, + }); + } + $popup.show(); + } + + // populateDescriptionFieldOptions(fieldId) { + // const valueField = this.CurrentView.valueField(); + // const $columnDescription = $$(this.ids.columnDescription); + + // const connectFieldOpts = + // valueField?.datasourceLink + // ?.fields?.((f) => f.key != "connectObject") + // .map?.((f) => { + // return { + // id: f.id, + // value: f.label, + // }; + // }) ?? []; + // $columnDescription.define("options", connectFieldOpts); + // $columnDescription.define("value", fieldId); + // $columnDescription.refresh(); + // } + + defaultValues() { + const ViewClass = this.ViewClass(); + + let values = null; + + if (ViewClass) { + values = ViewClass.defaultValues(); + } + + return values; + } + + /** + * @method values + * return the values for this form. + * @return {obj} + */ + values() { + const values = super.values(); + const ids = this.ids; + const settings = (values.settings = Object.assign( + $$(ids.component).getValues(), + values.settings + )); + // Retrive the values of your properties from Webix and store them in the view + settings.teamLink = $$(ids.teamLink).getValue(); + settings.teamName = $$(ids.teamName).getValue(); + settings.topTeam = $$(ids.topTeam).getValue(); + settings.teamInactive = $$(ids.teamInactive).getValue(); + settings.teamCanInactivate = $$(ids.teamCanInactivate).getValue(); + settings.teamStrategy = $$(ids.teamStrategy).getValue(); + settings.subStrategy = $$(ids.subStrategy).getValue(); + settings.strategyCode = $$(ids.strategyCode).getValue(); + settings.dataCollectionId = $$(ids.datacollectionID).getValue(); + settings.contentField = $$(ids.contentField).getValue(); + settings.contentGroupByField = $$(ids.contentGroupByField).getValue(); + settings.editContentFieldsToCreateNew = $$( + ids.editContentFieldsToCreateNew + ).getValue(); + settings.setEditableContentFields = $$( + ids.setEditableContentFields + ).getValue(); + settings.contentFieldFilter = JSON.stringify( + this.contentFieldFilter.getValue() + ); + settings.contentDisplayedFields = $$( + ids.contentDisplayedFields + ).getValues(); + settings.contentDisplayedFieldTypes = $$( + ids.contentDisplayedFieldTypes + ).getValues(); + settings.contentDisplayedFieldMappingData = $$( + ids.contentDisplayedFieldMappingData + ).getValues(); + settings.contentDisplayedFieldFilters = $$( + ids.contentDisplayedFieldFilters + ).getValues(); + settings.contentFieldDateStart = $$( + ids.contentFieldDateStart + ).getValue(); + settings.contentFieldDateEnd = $$(ids.contentFieldDateEnd).getValue(); + settings.dataPanelDCs = $$(ids.dataPanelDCs).getValues(); + const $colorForm = $$(ids.strategyColorForm); + settings.strategyColors = + $colorForm?.getValues() ?? settings.strategyColors; + return values; + } + + /** + * @method FieldClass() + * A method to return the proper ABViewXXX Definition. + * NOTE: Must be overwritten by the Child Class + */ + ViewClass() { + return super._ViewClass("orgchart_teams"); + } + } + + return ABViewOrgChartTeamsProperty; +} + +function fieldToOption(f) { + return { + id: f.id, + value: f.label, + field: f, + }; +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject.js b/src/rootPages/Designer/ui_work_object_list_newObject.js index 16a6cc11..94b870fa 100644 --- a/src/rootPages/Designer/ui_work_object_list_newObject.js +++ b/src/rootPages/Designer/ui_work_object_list_newObject.js @@ -22,6 +22,7 @@ import UIBlankObject from "./ui_work_object_list_newObject_blank"; import UICsvObject from "./ui_work_object_list_newObject_csv"; import UIApiObject from "./ui_work_object_list_newObject_api"; import UIImportObject from "./ui_work_object_list_newObject_import"; +import UINetsuiteObject from "./ui_work_object_list_newObject_netsuite"; // const ABImportExternal = require("./ab_work_object_list_newObject_external"); export default function (AB) { const UIClass = UI_Class(AB); @@ -42,6 +43,7 @@ export default function (AB) { this.CsvTab = UICsvObject(AB); this.ApiTab = UIApiObject(AB); this.ImportTab = UIImportObject(AB); + this.NetsuiteTab = UINetsuiteObject(AB); /* this.ExternalTab = new ABImportExternal(AB); */ @@ -86,10 +88,11 @@ export default function (AB) { view: "tabview", id: this.ids.tab, cells: [ - this.BlankTab.ui() /*, this.ImportTab.ui(), this.ExternalTab.ui() */, + this.BlankTab.ui() /* this.ExternalTab.ui() */, this.CsvTab.ui(), this.ApiTab.ui(), this.ImportTab.ui(), + this.NetsuiteTab.ui(), ], tabbar: { on: { @@ -129,7 +132,9 @@ export default function (AB) { "BlankTab", "CsvTab", "ApiTab", - "ImportTab" /*, "ExternalTab"*/, + "ImportTab", + "NetsuiteTab", + /*, "ExternalTab"*/ ].forEach((k) => { allInits.push(this[k].init(AB)); this[k].on("cancel", () => { @@ -161,6 +166,7 @@ export default function (AB) { this.CsvTab.applicationLoad(application); this.ApiTab.applicationLoad(application); this.ImportTab.applicationLoad(application); + this.NetsuiteTab.applicationLoad(application); } /** @@ -285,6 +291,9 @@ export default function (AB) { case this.ExternalTab?.ids.form: this.ExternalTab?.onShow?.(this.CurrentApplicationID); break; + case this.NetsuiteTab?.ids.form: + this.NetsuiteTab?.onShow?.(this.CurrentApplicationID); + break; } } } diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js b/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js index 2d0275d0..147f9e85 100644 --- a/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js +++ b/src/rootPages/Designer/ui_work_object_list_newObject_api_read_response.js @@ -79,7 +79,7 @@ export default function (AB) { label: L("Data Key"), placeholder: "data.example", bottomLabel: L( - "* JSON key containing the relevant data from the resonse object. Can be left blank to use the root level data." + "* JSON key containing the relevant data from the response object. Can be left blank to use the root level data." ), suggest: [], on: { @@ -261,12 +261,12 @@ export default function (AB) { _addFieldItem(key, type) { const uiItem = this._fieldItem(key, type); - $$(this.ids.fields).addView(uiItem); + $$(this.ids.connections).addView(uiItem); } _clearFieldItems() { - const $fields = $$(this.ids.fields); - AB.Webix.ui([], $fields); + const $connections = $$(this.ids.connections); + AB.Webix.ui([], $connections); } _populateDataKeys() { diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite.js new file mode 100644 index 00000000..32018fb8 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite.js @@ -0,0 +1,484 @@ +/* + * ui_work_object_list_newObject_netsuite + * + * Display the form for creating a new ABObject that connects to a Netsuite + * instance. + */ +import UI_Class from "./ui_class"; +import UI_Credentials from "./ui_work_object_list_newObject_netsuite_credentials"; +import UI_Tables from "./ui_work_object_list_newObject_netsuite_tables"; +import UI_Fields from "./ui_work_object_list_newObject_netsuite_fields"; +import UI_Connections from "./ui_work_object_list_newObject_netsuite_connections"; +import UI_FieldTest from "./ui_work_object_list_newObject_netsuite_dataTest"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite", { + // component: base, + + form: "", + buttonSave: "", + buttonCancel: "", + }); + + this.UI_Credentials = UI_Credentials(AB); + this.UI_Tables = UI_Tables(AB); + this.UI_Fields = UI_Fields(AB); + this.UI_FieldTest = UI_FieldTest(AB); + this.UI_Connections = UI_Connections(AB); + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Netsuite"), + body: { + view: "form", + id: this.ids.form, + width: 820, + height: 650, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + rows: [ + { + view: "text", + label: L("Name"), + name: "name", + required: true, + placeholder: L("Object name"), + labelWidth: 70, + }, + { + view: "checkbox", + label: L("Read Only"), + name: "readonly", + value: 0, + // disabled: true, + }, + ], + }, + { + view: "tabview", + cells: [ + this.UI_Credentials.ui(), + this.UI_Tables.ui(), + this.UI_Fields.ui(), + this.UI_Connections.ui(), + this.UI_FieldTest.ui(), + ], + }, + { fillspace: true }, + { + cols: [ + { fillspace: true }, + { + view: "button", + id: this.ids.buttonCancel, + value: L("Cancel"), + css: "ab-cancel-button", + autowidth: true, + click: () => { + this.cancel(); + }, + }, + { + view: "button", + id: this.ids.buttonSave, + css: "webix_primary", + value: L("Save"), + autowidth: true, + type: "form", + click: () => { + return this.save(); + }, + disabled: true, + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + AB.Webix.extend(this.$form, webix.ProgressBar); + + this.UI_Credentials.init(AB); + this.UI_Tables.init(AB); + this.UI_Fields.init(AB); + this.UI_Connections.init(AB); + this.UI_FieldTest.init(AB); + + this.UI_Credentials.show(); + this.UI_Tables.disable(); + this.UI_Fields.disable(); + this.UI_Connections.disable(); + this.UI_FieldTest.disable(); + + // "verified" is triggered on the credentials tab once we verify + // the entered data is successful. + this.UI_Credentials.on("verified", () => { + this.UI_Tables.enable(); + let creds = this.UI_Credentials.credentials(); + this.UI_Tables.setCredentials(creds); + this.UI_Fields.setCredentials(creds); + this.UI_FieldTest.setCredentials(creds); + this.UI_Connections.setCredentials(creds); + this.UI_Tables.show(); + }); + + this.UI_Credentials.on("notverified", () => { + this.UI_Tables.disable(); + }); + + this.UI_Tables.on("tables", (tables) => { + this.UI_Connections.setAllTables(tables); + }); + + this.UI_Tables.on("table.selected", (table) => { + this.UI_Fields.enable(); + this.UI_Fields.loadFields(table); + this.UI_Fields.show(); + + this.UI_Connections.setTable(table); + this.UI_FieldTest.setTable(table); + }); + + this.UI_Fields.on("connections", (list) => { + this.UI_Connections.loadConnections(list); + this.UI_Connections.enable(); + }); + + this.UI_Fields.on("fields.ready", (config) => { + this.UI_FieldTest.enable(); + this.UI_FieldTest.loadConfig(config); + }); + + this.UI_FieldTest.on("data.verfied", () => { + $$(this.ids.buttonSave).enable(); + }); + + // "save.error" is triggered by the ui_work_object_list_newObject + // if there was an error saving the values from our form. + this.on("save.error", (err) => { + this.onError(err); + }); + + // "save.successful" is triggered by the ui_work_object_list_newObject + // if the values we provided were successfully saved. + this.on("save.successful", async (obj) => { + this.onSuccess(); + + // try { + // await obj.fetchData(); + + // webix.message({ + // type: "success", + // text: L("Successfully fetching data."), + // }); + // } catch (err) { + // webix.message({ + // type: "error", + // text: L("Error fetching data."), + // }); + // this.AB.notify.developer(err, { + // context: "ABObjectAPI.fetchData()", + // object: obj.toObj(), + // }); + // } + }); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + cancel() { + this.formClear(); + this.emit("cancel"); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + this.UI_Credentials.formClear(); + this.UI_Tables.formClear(); + this.UI_Fields.formClear(); + this.UI_Connections.formClear(); + this.UI_FieldTest.formClear(); + + $$(this.ids.buttonSave).disable(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonSave).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonSave).enable(); + } + + /** + * @function save + * + * verify the current info is ok, package it, and return it to be + * added to the application.createModel() method. + */ + async save() { + this.busy(); + + const Form = this.$form; + + Form.clearValidation(); + + // if it doesn't pass the basic form validation, return: + if (!Form.validate()) { + this.ready(); + return false; + } + + let values = Form.getValues(); + + values.credentials = this.UI_Credentials.getValues(); + values.tableName = this.UI_Tables.getValues(); + + let allFields = this.UI_Fields.getValues(); + + // Pick out our special columns: pk, created_at, updated_at + let pkField = allFields.find((f) => f.pk); + if (!pkField) { + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: L("No primary key specified."), + type: "alert-error", + }); + return; + } + values.primaryColumnName = pkField.column; + + values.columnRef = { created_at: null, updated_at: null }; + + ["created_at", "updated_at"].forEach((field) => { + let foundField = allFields.find((f) => f[field]); + if (foundField) { + values.columnRef[field] = foundField.column; + } + }); + + // Create a new Object + const object = AB.objectNew( + Object.assign({ isNetsuite: true }, values) + ); + + try { + // Add fields + + for (const f of allFields) { + let def = { + name: f.title, + label: f.title, + columnName: f.column, + key: f.abType, + }; + if (f.default) { + def.settings = {}; + def.settings.default = f.default; + } + const field = AB.fieldNew(def, object); + await field.save(true); + + // values.fieldIDs.push(field.id); + } + // values.id = object.id; + } catch (err) { + console.error(err); + } + + let allConnectFields = this.UI_Connections.getValues(); + for (var i = 0; i < allConnectFields.length; i++) { + let f = allConnectFields[i]; + /* f = + { + "thisField": "_this_object_", + "thatObject": "b7c7cca2-b919-4a90-b199-650a7a4693c1", + "thatObjectField": "custrecord_whq_teams_strategy_strtgy_cod", + "linkType": "many:one" + } + */ + + let linkObject = this.AB.objectByID(f.thatObject); + if (!linkObject) continue; + + let linkType = f.linkType; + let parts = linkType.split(":"); + let link = parts[0]; + let linkVia = parts[1]; + + let thisField = { + key: "connectObject", + // columnName: f.thisField, + label: linkObject.label, + settings: { + showIcon: "1", + + linkObject: linkObject.id, + linkType: link, + linkViaType: linkVia, + isCustomFK: 0, + indexField: "", + indexField2: "", + isSource: 0, + width: 100, + }, + }; + + let linkField = this.AB.cloneDeep(thisField); + // linkField.columnName = f.thatObjectField; + linkField.label = object.label || object.name; + linkField.settings.linkObject = object.id; + linkField.settings.linkType = linkVia; + linkField.settings.linkViaType = link; + + switch (linkType) { + case "one:one": + if (f.whichSource == "_this_") { + thisField.settings.isSource = 1; + } else { + linkField.settings.isSource = 1; + } + thisField.columnName = f.sourceField; + linkField.columnName = f.sourceField; + break; + + case "one:many": + case "many:one": + thisField.columnName = f.thatField; + linkField.columnName = f.thatField; + break; + + case "many:many": + thisField.settings.joinTable = f.joinTable; + linkField.settings.joinTable = f.joinTable; + + thisField.settings.joinTableReference = f.thisObjReference; + linkField.settings.joinTableReference = f.thatObjReference; + + thisField.settings.joinTablePK = f.joinTablePK; + linkField.settings.joinTablePK = f.joinTablePK; + + thisField.settings.joinTableEntity = f.joinTableEntity; + linkField.settings.joinTableEntity = f.joinTableEntity; + + if (f.joinActiveField != "_none_") { + thisField.settings.joinActiveField = f.joinActiveField; + thisField.settings.joinActiveValue = f.joinActiveValue; + thisField.settings.joinInActiveValue = f.joinInActiveValue; + + linkField.settings.joinActiveField = f.joinActiveField; + linkField.settings.joinActiveValue = f.joinActiveValue; + linkField.settings.joinInActiveValue = f.joinInActiveValue; + } + break; + } + + // create an initial LinkColumn + let fieldLink = linkObject.fieldNew(linkField); + await fieldLink.save(true); // should get an .id now + + // make sure I can reference field => linkColumn + thisField.settings.linkColumn = fieldLink.id; + let field = object.fieldNew(thisField); + await field.save(); + + // now update reference linkColumn => field + fieldLink.settings.linkColumn = field.id; + await fieldLink.save(); + } + + this.emit("save", object.toObj()); + + this.ready(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonSave); + + $form.showProgress({ type: "icon" }); + $saveButton.disable(); + } + + ready() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonSave); + + $form.hideProgress(); + $saveButton.enable(); + } + } + return new UI_Work_Object_List_NewObject_Netsuite(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_connections.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_connections.js new file mode 100644 index 00000000..bdae1a56 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_connections.js @@ -0,0 +1,907 @@ +/* + * ui_work_object_list_newObject_netsuite_connections + * + * Display the tab/form for selecting which of the available conections we + * want to create for this table. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + const uiConfig = AB.Config.uiSettings(); + + class UI_Work_Object_List_NewObject_Netsuite_Connections extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_connections", { + // component: base, + + form: "", + + // fieldSelector: "", + connections: "", + displayConnections: "", + displayNoConnections: "", + + fieldGrid: "", + buttonVerify: "", + buttonLookup: "", + tableName: "", + }); + + this.allTables = []; + // [ { id, name }, ... ] + // A list of all the available tables. This is used for identifying the + // join tables in many:many connections. + // We get this list from the Tables interface tab. + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.connectionList = null; + // {array} + // Holds an array of connection descriptions + + this.connections = null; + // {array} + // Holds the array of chosen/verified connections + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Connections"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 450, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + view: "layout", + padding: 10, + rows: [ + { + id: this.ids.tableName, + label: L("Selected Table: {0}", [this.table]), + view: "label", + height: 40, + }, + ], + }, + + // Field Selector + { + view: "multiview", + animate: false, + borderless: true, + rows: [ + { + id: this.ids.displayNoConnections, + rows: [ + { + maxHeight: uiConfig.xxxLargeSpacer, + hidden: uiConfig.hideMobile, + }, + { + view: "label", + align: "center", + height: 200, + label: "
", + }, + { + // id: ids.error_msg, + view: "label", + align: "center", + label: L( + "You have no other Netwuite Objects imported" + ), + }, + { + // id: ids.error_msg, + view: "label", + align: "center", + label: L( + "Continue creating this object now, then create the connections on the other objects you import." + ), + }, + { + maxHeight: uiConfig.xxxLargeSpacer, + hidden: uiConfig.hideMobile, + }, + ], + }, + { + id: this.ids.displayConnections, + rows: [ + { + // id: ids.tabFields, + view: "layout", + padding: 10, + rows: [ + { + cols: [ + { + label: L("Connections"), + view: "label", + }, + { + icon: "wxi-plus", + view: "icon", + width: 38, + click: () => { + this._addConnection(); + }, + }, + ], + }, + { + view: "scrollview", + scroll: "y", + borderless: true, + padding: 0, + margin: 0, + body: { + id: this.ids.connections, + view: "layout", + padding: 0, + margin: 0, + rows: [], + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + + AB.Webix.extend(this.$form, webix.ProgressBar); + + // this.$fieldSelector = $$(this.ids.fieldSelector); + // if (this.$fieldSelector) + // AB.Webix.extend(this.$fieldSelector, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + this.disable(); + } + + getValues() { + return []; // TODO: + } + + setTable(table) { + this.table = table; + $$(this.ids.tableName).setValue( + `${this.table}` + ); + } + + loadConnections(allConnections) { + this.connectionList = allConnections; + // refresh more often than on init(); + this.listNetsuiteObjects = this.AB.objects((o) => o.isNetsuite); + if (this.listNetsuiteObjects.length == 0) { + $$(this.ids.displayNoConnections)?.show(); + } else { + $$(this.ids.displayConnections)?.show(); + } + } + + _fieldItem(key, type) { + const self = this; + const fieldTypes = this.AB.Class.ABFieldManager.allFields(); + const fieldKeys = ["string", "LongText", "number", "date", "boolean"]; + + const linkTypes = ["one:one", "one:many", "many:one", "many:many"]; + const linkOptions = linkTypes.map((l) => { + return { id: l, value: l }; + }); + linkOptions.unshift({ id: "_choose", value: L("choose link type") }); + + // For the Base Object, let's include all fields that are clearly + // objects. + let fieldOptions = this.connectionList.map((conn) => { + return { + id: conn.column, + value: conn.column, + }; + }); + + let thisObjectFields = fieldOptions; + let thatObjectFields = []; + + let listOtherObjects = this.listNetsuiteObjects.map((nObj) => { + return { + id: nObj.id, + value: nObj.label, + }; + }); + listOtherObjects.unshift({ id: "_choose", value: L("Choose Object") }); + + return { + view: "form", + elements: [ + { + cols: [ + // object and type + { + rows: [ + { + placeholder: L("Existing Netsuite Object"), + options: listOtherObjects, + view: "select", + name: "thatObject", + label: L("To:"), + // value: type, + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + let connObj = self.AB.objectByID(newVal); + if (connObj) { + let result = await self.AB.Network.get({ + url: `/netsuite/table/${connObj.tableName}`, + params: { + credentials: JSON.stringify( + self.credentials + ), + }, + }); + let fields = result.filter( + (r) => r.type == "object" + ); + let options = fields.map((f) => { + return { + id: f.column, + value: f.column, + }; + }); + + // include a "_that_object_" incase this is a one:xxx + // connection. + // options.unshift({ + // id: "_that_object_", + // value: L("That Object"), + // }); + + thatObjectFields = options; + /* + let $linkColumn = + this.getParentView().getChildViews()[1]; + + $linkColumn.define("options", options); + $linkColumn.refresh(); + */ + let $rowsFieldsets = this.getParentView() + .getParentView() + .getChildViews()[1]; + + // update one:one ThatObject: + let whichOptions = $rowsFieldsets + .getChildViews()[0] + .getChildViews()[0] + .getChildViews()[1]; + let newOptions = [ + { id: "_choose", value: L("Choose") }, + { + id: "_this_", + value: L("This Object"), + }, + ]; + newOptions.push({ + id: connObj.id, + value: connObj.label, + }); + whichOptions.define( + "options", + newOptions + ); + whichOptions.refresh(); + } + }, + }, + }, + { + placeholder: "Link Type", + options: linkOptions, + view: "select", + name: "linkType", + label: L("link type"), + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + let $toObj = + this.getParentView().getChildViews()[0]; + let $linkColumn = + this.getParentView().getChildViews()[1]; + + let objID = $toObj.getValue(); + let Obj = self.AB.objectByID(objID); + + let linkVal = $linkColumn.getValue(); + let links = linkVal.split(":"); + let messageA = self.message( + L("This object"), + links[0], + Obj.label + ); + + let messageB = self.message( + Obj.label, + links[1], + L("This object") + ); + + if (newVal == "_choose") { + messageA = messageB = ""; + } + + let $linkTextA = + this.getParentView().getChildViews()[2]; + let $linkTextB = + this.getParentView().getChildViews()[3]; + + $linkTextA.define("label", messageA); + $linkTextA.refresh(); + + $linkTextB.define("label", messageB); + $linkTextB.refresh(); + + let $rowsFieldsets = this.getParentView() + .getParentView() + .getChildViews()[1]; + + let $thatFieldOptions; + + switch (linkVal) { + case "one:one": + $rowsFieldsets + .getChildViews()[0] + .show(); + break; + + case "one:many": + // This Object's fields must be in field picker: + $thatFieldOptions = $rowsFieldsets + .getChildViews()[1] + .getChildViews()[0] + .getChildViews()[1]; + $thatFieldOptions.define( + "options", + thisObjectFields + ); + $thatFieldOptions.refresh(); + $rowsFieldsets + .getChildViews()[1] + .show(); + break; + + case "many:one": + // This Object's fields must be in field picker: + $thatFieldOptions = $rowsFieldsets + .getChildViews()[1] + .getChildViews()[0] + .getChildViews()[1]; + $thatFieldOptions.define( + "options", + thatObjectFields + ); + $thatFieldOptions.refresh(); + $rowsFieldsets + .getChildViews()[1] + .show(); + break; + + case "many:many": + $rowsFieldsets + .getChildViews()[2] + .show(); + break; + } + }, + }, + // value: type, + }, + { + // this to that + // id: ids.fieldLink2, + view: "label", + // width: 200, + }, + { + // that to this + view: "label", + // width: 200, + }, + ], + }, + { + rows: [ + { + view: "fieldset", + label: L("one to one"), + hidden: true, + body: { + rows: [ + { + view: "label", + label: L( + "which object holds the connection value?" + ), + }, + { + view: "select", + options: [ + { + id: "_choose", + value: L("Choose Object"), + }, + { + id: "_this_", + value: L("This Object"), + }, + { + id: "_that_", + value: L("That Object"), + }, + ], + name: "whichSource", + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + if (newVal == "_choose") return; + + let $fieldPicker = + this.getParentView().getChildViews()[2]; + + if (newVal == "_this_") { + $fieldPicker.define( + "options", + thisObjectFields + ); + } else { + $fieldPicker.define( + "options", + thatObjectFields + ); + } + $fieldPicker.refresh(); + $fieldPicker.show(); + }, + }, + }, + { + view: "select", + label: L("which field"), + name: "sourceField", + options: [], + hidden: true, + }, + ], + }, + }, + { + view: "fieldset", + label: L("one:X"), + hidden: true, + body: { + rows: [ + { + view: "label", + label: L( + "which field defines the connection?" + ), + }, + { + view: "select", + // label: L("which field"), + name: "thatField", + options: [], + // hidden: false, + }, + ], + }, + }, + { + view: "fieldset", + label: L("many:many"), + hidden: true, + body: { + rows: [ + { + view: "label", + label: L( + "which table is the join table?" + ), + }, + { + view: "combo", + name: "joinTable", + options: { + filter: (item, value) => { + return ( + item.value + .toLowerCase() + .indexOf( + value.toLowerCase() + ) > -1 + ); + }, + body: { + // template: "#value#", + data: this.allTables, + }, + }, + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + let result = + await self.AB.Network.get({ + url: `/netsuite/table/${newVal}`, + params: { + credentials: + JSON.stringify( + self.credentials + ), + }, + }); + // let fields = result.filter( + // (r) => r.type == "object" + // ); + let options = result.map((f) => { + return { + id: f.column, + value: f.column, + }; + }); + + let $thisObjRef = + this.getParentView().getChildViews()[2]; + $thisObjRef.define( + "options", + options + ); + $thisObjRef.refresh(); + $thisObjRef.show(); + + let $thatObjRef = + this.getParentView().getChildViews()[3]; + $thatObjRef.define( + "options", + options + ); + $thatObjRef.refresh(); + $thatObjRef.show(); + + let $objectPK = + this.getParentView().getChildViews()[4]; + $objectPK.define( + "options", + options + ); + + let pkField = result.find( + (r) => r.title == "Internal ID" + ); + if (pkField) { + $objectPK.setValue( + pkField.column + ); + } + $objectPK.refresh(); + $objectPK.show(); + + let $entityField = + this.getParentView().getChildViews()[5]; + $entityField.define( + "options", + options + ); + + let fieldEntity = result.find( + (r) => { + if (!r.column) return false; + + return ( + r.column.indexOf( + "entity" + ) > -1 + ); + } + ); + if (fieldEntity) { + $entityField.setValue( + fieldEntity.column + ); + } + $entityField.refresh(); + $entityField.show(); + + let fOptions = + self.AB.cloneDeep(options); + fOptions.unshift({ + id: "_none_", + value: "", + }); + let $activeField = + this.getParentView().getChildViews()[6]; + $activeField.define( + "options", + fOptions + ); + $activeField.refresh(); + $activeField.show(); + }, + }, + }, + + { + view: "select", + label: L("This Object's reference"), + labelPosition: "top", + options: [], + name: "thisObjReference", + hidden: true, + }, + { + view: "select", + label: L("That Object's reference"), + labelPosition: "top", + options: [], + name: "thatObjReference", + hidden: true, + }, + { + view: "select", + label: L("Join Table Primary Key:"), + labelPosition: "top", + options: [], + name: "joinTablePK", + hidden: true, + }, + { + view: "select", + label: L( + "Which field holds the Entity:" + ), + labelPosition: "top", + options: [], + name: "joinTableEntity", + hidden: true, + }, + { + view: "select", + label: L("Join Table isActive Field:"), + labelPosition: "top", + options: [], + name: "joinActiveField", + hidden: true, + on: { + onChange: async function ( + newVal, + oldVal, + config + ) { + if (newVal != "_none_") { + // show the active/inactive value + let siblings = + this.getParentView().getChildViews(); + siblings[ + siblings.length - 2 + ].show(); + siblings[ + siblings.length - 1 + ].show(); + } + }, + }, + }, + { + view: "text", + label: L("Active Value"), + name: "joinActiveValue", + hidden: true, + value: "", + }, + { + view: "text", + label: L("InActive Value"), + name: "joinInActiveValue", + hidden: true, + value: "", + }, + ], + }, + }, + ], + }, + { + // Delete Column + rows: [ + {}, + { + icon: "wxi-trash", + view: "icon", + width: 38, + click: function () { + const $item = this.getParentView() + .getParentView() + .getParentView(); + $$(self.ids.connections).removeView($item); + }, + }, + {}, + ], + // delete Row Icon + }, + ], + }, + ], + }; + } + + _addConnection(key, type) { + const uiItem = this._fieldItem(key, type); + $$(this.ids.connections).addView(uiItem); + } + + _clearFieldItems() { + const $connections = $$(this.ids.connections); + AB.Webix.ui([], $connections); + } + + message(a, link, b) { + let msg; + if (link == "many") { + msg = L("{0} has many {1} entities", [a, b]); + } else { + msg = L("{0} has one {1} entity", [a, b]); + } + + return msg; + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $verifyButton = $$(this.ids.buttonVerify); + + // this.$fieldSelector.showProgress({ type: "icon" }); + $verifyButton.disable(); + } + + ready() { + const $verifyButton = $$(this.ids.buttonVerify); + + // this.$fieldSelector.hideProgress(); + $verifyButton.enable(); + } + + setCredentials(creds) { + this.credentials = creds; + } + + setAllTables(tables) { + this.allTables = this.AB.cloneDeep(tables); + this.allTables.unshift({ id: "_choose", value: L("Choose") }); + } + + getValues() { + let values = []; + $$(this.ids.connections) + .getChildViews() + .forEach(($row) => { + values.push($row.getValues()); + }); + return values; + } + + // verify() { + // this.emit("fields.ready", { + // credentials: this.credentials, + // table: this.table, + // fieldList: this.fieldList, + // }); + // } + } + return new UI_Work_Object_List_NewObject_Netsuite_Connections(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_credentials.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_credentials.js new file mode 100644 index 00000000..e4fbf138 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_credentials.js @@ -0,0 +1,317 @@ +/* + * ui_work_object_list_newObject_netsuite_credentials + * + * Display the tab/form for entering the Netsuite credentials for our + * connection. + */ +import UI_Class from "./ui_class"; + +const KeysCredentials = [ + "NETSUITE_CONSUMER_KEY", + "NETSUITE_CONSUMER_SECRET", + "NETSUITE_TOKEN_KEY", + "NETSUITE_TOKEN_SECRET", +]; +const KeysAPI = [ + "NETSUITE_REALM", + "NETSUITE_BASE_URL", + "NETSUITE_QUERY_BASE_URL", +]; + +const KeysALL = KeysCredentials.concat(KeysAPI); + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_Credentials extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_credentials", { + // component: base, + + form: "", + buttonVerify: "", + labelVerified: "", + }); + + this.entries = {}; + } + + ui() { + // Our webix UI definition: + let ui = { + id: this.ids.component, + header: L("Credentials"), + body: { + view: "form", + id: this.ids.form, + width: 820, + height: 700, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + rows: [], + }, + { + cols: [ + { fillspace: true }, + // { + // view: "button", + // id: this.ids.buttonCancel, + // value: L("Cancel"), + // css: "ab-cancel-button", + // autowidth: true, + // click: () => { + // this.cancel(); + // }, + // }, + { + view: "button", + id: this.ids.buttonVerify, + css: "webix_primary", + value: L("Verify"), + autowidth: true, + type: "form", + click: () => { + return this.verify(); + }, + }, + ], + }, + { + cols: [ + { fillspace: true }, + { + id: this.ids.labelVerified, + view: "label", + label: L( + "All parameters are valid. Continue on to select a Table to work with." + ), + hidden: true, + }, + ], + }, + ], + }, + }; + + let rows = ui.body.elements[0].rows; + let fsOauth = { + view: "fieldset", + label: L("Netsuite OAuth 1.0 Credentials"), + body: { + rows: [], + }, + }; + + let EnvInput = (k) => { + return { + cols: [ + { + id: k, + view: "text", + label: k, + name: k, + required: true, + placeholder: `ENV:${k}`, + value: `ENV:${k}`, + labelWidth: 230, + on: { + onChange: (nV, oV) => { + this.envVerify(k, nV); + }, + }, + }, + { + id: `${k}_verified`, + view: "label", + width: 20, + label: '', + hidden: true, + }, + ], + }; + }; + + KeysCredentials.forEach((k) => { + fsOauth.body.rows.push(EnvInput(k)); + }); + rows.push(fsOauth); + rows.push({ height: 15 }); + + let fsAPI = { + view: "fieldset", + label: L("Netsuite API Config"), + body: { + rows: [], + }, + }; + + KeysAPI.forEach((k) => { + fsAPI.body.rows.push(EnvInput(k)); + }); + rows.push(fsAPI); + + return ui; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + AB.Webix.extend(this.$form, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + KeysALL.forEach((k) => { + $$(k).setValue(`ENV:${k}`); + }); + } + + getValues() { + return this.credentials(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + console.log("SHOW, Baby! SHOW!"); + } + + busy() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonVerify); + + $form.showProgress({ type: "icon" }); + $saveButton.disable(); + } + + credentials() { + let creds = {}; + KeysALL.forEach((k) => { + let $input = $$(k); + if ($input) { + creds[k] = $input.getValue(); + } + }); + + return creds; + } + + envVerify(k, nV) { + let envKey = nV.replace("ENV:", ""); + return this.AB.Network.get({ + url: `/env/verify/${envKey}`, + }) + .then((result) => { + console.log(result); + if (result.status == "success") { + $$(`${k}_verified`).show(); + this.entries[k] = true; + } else { + $$(`${k}_verified`).hide(); + this.entries[k] = false; + } + }) + .catch((err) => { + console.error(err); + $$(`${k}_verified`).hide(); + this.entries[k] = false; + }); + } + + ready() { + const $form = $$(this.ids.form); + const $saveButton = $$(this.ids.buttonVerify); + + $form.hideProgress(); + $saveButton.enable(); + } + + async verify() { + let isVerified = true; + let AllVerifies = []; + KeysALL.forEach((k) => { + let nV = $$(k).getValue(); + AllVerifies.push(this.envVerify(k, nV)); + }); + await Promise.all(AllVerifies); + + KeysALL.forEach((k) => { + isVerified = isVerified && this.entries[k]; + }); + + if (isVerified) { + this.emit("verified"); + $$(this.ids.labelVerified)?.show(); + } else { + this.emit("notverified"); + $$(this.ids.labelVerified)?.hide(); + } + } + } + return new UI_Work_Object_List_NewObject_Netsuite_Credentials(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_dataTest.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_dataTest.js new file mode 100644 index 00000000..a258d31f --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_dataTest.js @@ -0,0 +1,390 @@ +/* + * ui_work_object_list_newObject_netsuite_dataTest + * + * Display the tab/form to review what the current Definitions look like + * working with the data from Netsuite. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_DataTest extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_dataTest", { + // component: base, + form: "", + network: "", + dataView: "", + + buttonVerify: "", + tableName: "", + }); + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.fieldKeys = [ + "string", + "LongText", + "number", + "date", + "boolean", + "json", + "list", + ]; + // {array} of types of ABFields we can translate into. + + this.fieldList = null; + // {array} + // Holds an array of field descriptions + + this.table = null; + // {string} + // name of the table we are working with + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Data Verification"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 400, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + // Field Selector + { + view: "layout", + padding: 10, + rows: [ + { + cols: [ + { + id: this.ids.tableName, + label: L("Selected Table: {0}", [this.table]), + view: "label", + height: 40, + }, + {}, + ], + }, + { + view: "multiview", + // keepViews: true, + cells: [ + // Select Table indicator + { + id: this.ids.network, + rows: [ + {}, + { + view: "label", + align: "center", + height: 200, + label: "", + }, + { + view: "label", + align: "center", + label: L( + "Gathering data from Netsuite." + ), + }, + {}, + ], + }, + { + id: this.ids.dataView, + rows: [ + {}, + { + view: "label", + label: "Waiting for response", + }, + {}, + ], + // hidden: true, + }, + ], + }, + + // { + // id: this.ids.fieldGrid, + // view: "datatable", + // resizeColumn: true, + // height: 300, + // columns: [ + // { + // id: "title", + // header: L("title"), + // editor: "text", + // }, + // { id: "column", header: L("column") }, + + // { id: "nullable", header: L("nullable") }, + // { id: "readOnly", header: L("read only") }, + // { + // id: "pk", + // header: L("is primary key"), + // template: "{common.radio()}", + // }, + // // { + // // id: "description", + // // header: L("description"), + // // fillspace: true, + // // }, + // { + // id: "abType", + // header: L("AB Field Type"), + // editor: "select", + // options: [" "].concat(this.fieldKeys), + // }, + // { + // id: "delme", + // header: "", + // template: "{common.trashIcon()}", + // }, + // ], + // editable: true, + // scroll: "y", + // onClick: { + // "wxi-trash": (e, id) => { + // debugger; + // $$(this.ids.fieldGrid).remove(id); + // }, + // }, + // }, + ], + }, + + { + cols: [ + { fillspace: true }, + // { + // view: "button", + // id: this.ids.buttonCancel, + // value: L("Cancel"), + // css: "ab-cancel-button", + // autowidth: true, + // click: () => { + // this.cancel(); + // }, + // }, + { + view: "button", + id: this.ids.buttonVerify, + css: "webix_primary", + value: L("Verify"), + autowidth: true, + type: "form", + click: () => { + return this.verify(); + }, + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + AB.Webix.extend(this.$form, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + // reset the data view to blank + let table = { + id: this.ids.dataView, + rows: [ + {}, + { + view: "label", + label: "Waiting for response", + }, + {}, + ], + // hidden: true, + }; + webix.ui(table, $$(this.ids.dataView)); + this.disable(); + } + setTableName() { + $$(this.ids.tableName).setValue( + `${this.table}` + ); + } + + setTable(table) { + this.table = table; + this.setTableName(); + + // this is called when a table name has been selected. + // but we need to be disabled until they have verified the + // fields. + this.formClear(); + } + + async loadConfig(config) { + this.credentials = config.credentials; + this.setTable(config.table); + this.fieldList = config.fieldList; + + $$(this.ids.network).show(); + this.busy(); + + let result = await this.AB.Network.get({ + url: `/netsuite/dataVerify/${this.table}`, + params: { + credentials: JSON.stringify(this.credentials), + }, + }); + + this.data = result; + // this.ids.dataView, + + // convert all the json types to strings for display: + this.fieldList + .filter((f) => f.abType == "json") + .forEach((f) => { + this.data.forEach((d) => { + try { + d[f.column] = JSON.stringify(d[f.column]); + } catch (e) { + console.log(e); + } + }); + }); + + this.showTable(); + this.enable(); + this.ready(); + } + + showTable() { + let table = { + id: this.ids.dataView, + view: "datatable", + columns: this.fieldList.map((f) => { + return { + id: f.column, + header: f.title, + }; + }), + data: this.data, + }; + + webix.ui(table, $$(this.ids.dataView)); + $$(this.ids.dataView).show(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + // onError(err) { + // if (err) { + // console.error(err); + // let message = L("the entered data is invalid"); + // // if this was our Validation() object: + // if (err.updateForm) { + // err.updateForm(this.$form); + // } else { + // if (err.code && err.data) { + // message = err.data?.sqlMessage ?? message; + // } else { + // message = err?.message ?? message; + // } + // } + + // const values = this.$form.getValues(); + // webix.alert({ + // title: L("Error creating Object: {0}", [values.name]), + // ok: L("fix it"), + // text: message, + // type: "alert-error", + // }); + // } + // // get notified if there was an error saving. + // $$(this.ids.buttonVerify).enable(); + // } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + // onSuccess() { + // this.formClear(); + // $$(this.ids.buttonVerify).enable(); + // } + + verify() { + this.emit("data.verfied"); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$form.showProgress({ type: "icon" }); + $verifyButton.disable(); + } + + ready() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$form.hideProgress(); + $verifyButton.enable(); + } + + setCredentials(creds) { + this.credentials = creds; + } + } + return new UI_Work_Object_List_NewObject_Netsuite_DataTest(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_fields.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_fields.js new file mode 100644 index 00000000..08caf392 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_fields.js @@ -0,0 +1,419 @@ +/* + * ui_work_object_list_newObject_netsuite_fields + * + * Display the tab/form for selecting which of the available tables we are + * working with. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_Fields extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_fields", { + // component: base, + + form: "", + + tableName: "", + // tableList: "", + fieldSelector: "", + // fields: "", + fieldGrid: "", + buttonVerify: "", + // buttonLookup: "", + }); + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.fieldKeys = [ + "string", + "LongText", + "number", + "date", + "datetime", + "boolean", + "json", + "list", + // "connectObject", + ]; + // {array} of types of ABFields we can translate into. + + this.fieldList = null; + // {array} + // Holds an array of field descriptions + + this.fields = null; + // {array} + // Holds the array of chosen/verified fields + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Fields"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 450, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + // Field Selector + { + id: this.ids.fieldSelector, + view: "layout", + padding: 10, + rows: [ + { + rows: [ + { + id: this.ids.tableName, + label: L("Selected Table: {0}", [this.table]), + view: "label", + height: 40, + }, + {}, + ], + }, + // { + // view: "scrollview", + // scroll: "y", + // borderless: true, + // padding: 0, + // margin: 0, + // body: { + // id: this.ids.fields, + // view: "layout", + // padding: 0, + // margin: 0, + // rows: [], + // }, + // }, + { + id: this.ids.fieldGrid, + view: "datatable", + resizeColumn: true, + height: 300, + columns: [ + { + id: "title", + header: L("title"), + editor: "text", + }, + { id: "column", header: L("column") }, + + { id: "nullable", header: L("nullable") }, + { id: "readOnly", header: L("read only") }, + { + id: "default", + header: L("Default Value"), + editor: "text", + }, + { + id: "pk", + header: L("is primary key"), + template: "{common.radio()}", + }, + { + id: "created_at", + header: L("Created At"), + template: "{common.radio()}", + }, + { + id: "updated_at", + header: L("Updated At"), + template: "{common.radio()}", + }, + // { + // id: "description", + // header: L("description"), + // fillspace: true, + // }, + { + id: "abType", + header: L("AB Field Type"), + editor: "select", + options: [" "].concat(this.fieldKeys), + on: { + onChange: (newValue, oldValue) => { + debugger; + }, + }, + }, + { + id: "delme", + header: "", + template: "{common.trashIcon()}", + }, + ], + editable: true, + scroll: "xy", + onClick: { + "wxi-trash": (e, id) => { + $$(this.ids.fieldGrid).remove(id); + this.fields = this.fields.filter( + (f) => f.id != id.row + ); + }, + }, + }, + { + cols: [ + { fillspace: true }, + // { + // view: "button", + // id: this.ids.buttonCancel, + // value: L("Cancel"), + // css: "ab-cancel-button", + // autowidth: true, + // click: () => { + // this.cancel(); + // }, + // }, + { + view: "button", + id: this.ids.buttonVerify, + css: "webix_primary", + value: L("Verify"), + autowidth: true, + type: "form", + click: () => { + return this.verify(); + }, + }, + ], + }, + ], + }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + + this.$fieldSelector = $$(this.ids.fieldSelector); + AB.Webix.extend(this.$form, webix.ProgressBar); + AB.Webix.extend(this.$fieldSelector, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + $$(this.ids.fieldGrid)?.clearAll(); + this.disable(); + $$(this.ids.buttonVerify).disable(); + } + + addABType(f) { + switch (f.type) { + case "array": + // this is most likely a MANY:x connection. + // Q:what do we default this to? + f.abType = "json"; + break; + + case "object": + // this is most likely a ONE:X[ONE,MANY] connection. + // // lets scan the properties of the dest obj, + // // find a property with title = "Internal Identifier" + // // and make this ABType == that property.type + + // if (f.properties) { + // Object.keys(f.properties).forEach((k) => { + // if (f.properties[k].title == "Internal Identifier") { + // f.abType = f.properties[k].type; + // } + // }); + // } + // // default to "string" if an Internal Identifier isn't found. + // if (!f.abType) { + // f.abType = "string"; + // } + f.abType = "connectObject"; + break; + + case "boolean": + f.abType = "boolean"; + break; + + default: + f.abType = "string"; + } + + // just in case: + // lets see if this looks like a date field instead + + if (f.abType == "string") { + let lcTitle = f.title?.toLowerCase(); + if (lcTitle) { + let indxDate = lcTitle.indexOf("date") > -1; + let indxDay = lcTitle.indexOf("day") > -1; + if (indxDate || indxDay) { + f.abType = "date"; + } + } + + if (f.format == "date-time") { + f.abType = "datetime"; + } + } + + // Seems like the PKs have title == "Internal ID" + if (f.title == "Internal ID") { + f.pk = true; + } + } + + getValues() { + return this.fields; + } + + setTableName() { + $$(this.ids.tableName).setValue( + `${this.table}` + ); + } + async loadFields(table) { + $$(this.ids.fieldGrid)?.clearAll(); + this.table = table; + $$(this.ids.fieldSelector).show(); + this.busy(); + this.setTableName(); + + let result = await this.AB.Network.get({ + url: `/netsuite/table/${table}`, + params: { credentials: JSON.stringify(this.credentials) }, + }); + + this.fieldList = result; + (result || []).forEach((f) => { + this.addABType(f); + }); + + // ok, in this pane, we are just looking at the base fields + // leave the connections to the next pane: + this.fields = result.filter((r) => r.type != "object"); + + // let our other pane know about it's connections + this.emit( + "connections", + result.filter((r) => r.type == "object") + ); + + $$(this.ids.fieldGrid).parse(this.fields); + this.ready(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$fieldSelector.showProgress({ type: "icon" }); + $verifyButton.disable(); + } + + ready() { + const $verifyButton = $$(this.ids.buttonVerify); + + this.$fieldSelector.hideProgress(); + $verifyButton.enable(); + } + + setCredentials(creds) { + this.credentials = creds; + } + + verify() { + this.emit("fields.ready", { + credentials: this.credentials, + table: this.table, + fieldList: this.fields, + }); + } + } + return new UI_Work_Object_List_NewObject_Netsuite_Fields(); +} diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_tables.js b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_tables.js new file mode 100644 index 00000000..e3a62781 --- /dev/null +++ b/src/rootPages/Designer/ui_work_object_list_newObject_netsuite_tables.js @@ -0,0 +1,375 @@ +/* + * ui_work_object_list_newObject_netsuite_tables + * + * Display the tab/form for selecting which of the available tables we are + * working with. + */ +import UI_Class from "./ui_class"; + +export default function (AB) { + const UIClass = UI_Class(AB); + const L = UIClass.L(); + + class UI_Work_Object_List_NewObject_Netsuite_Tables extends UIClass { + constructor() { + super("ui_work_object_list_newObject_netsuite_tables", { + // component: base, + + form: "", + + searchText: "", + tableList: "", + // fieldSelector: "", + fields: "", + buttonVerify: "", + buttonLookup: "", + }); + + this.credentials = {}; + // { CRED_KEY : CRED_VAL } + // The entered credential references necessary to perform our Netsuite + // operations. + + this.lastSelectedTable = null; + // {string} + // the table name of the last selected table. + } + + ui() { + // Our webix UI definition: + return { + id: this.ids.component, + header: L("Tables"), + body: { + view: "form", + id: this.ids.form, + width: 800, + height: 700, + rules: { + // TODO: + // name: inputValidator.rules.validateObjectName + }, + elements: [ + { + cols: [ + // The Table Selector Column + { + rows: [ + { + cols: [ + { + view: "label", + align: "left", + label: L( + "Use the provided credentials to request a list of tables to work with." + ), + }, + { + view: "button", + id: this.ids.buttonLookup, + value: L("Load Catalog"), + // css: "ab-cancel-button", + autowidth: true, + click: () => { + this.loadCatalog(); + }, + }, + ], + }, + { + id: this.ids.searchText, + view: "search", + icon: "fa fa-search", + label: L("Search"), + labelWidth: 80, + placeholder: L("tablename"), + height: 35, + keyPressTimeout: 100, + on: { + onAfterRender() { + AB.ClassUI.CYPRESS_REF(this); + }, + onTimedKeyPress: () => { + let searchText = $$(this.ids.searchText) + .getValue() + .toLowerCase(); + + this.$list.filter(function (item) { + return ( + !item.value || + item.value + .toLowerCase() + .indexOf(searchText) > -1 + ); + }); + }, + }, + }, + { + id: this.ids.tableList, + view: "list", + select: 1, + height: 400, + on: { + onItemClick: (id, e) => { + if (id != this.lastSelectedTable) { + this.lastSelectedTable = id; + this.emit("table.selected", id); + } + }, + }, + }, + ], + }, + + // Select Table indicator + { + rows: [ + {}, + { + view: "label", + align: "center", + height: 200, + label: "", + }, + { + view: "label", + align: "center", + label: L("Select an table to work with."), + }, + {}, + ], + }, + ], + }, + // { + // cols: [ + // { fillspace: true }, + // // { + // // view: "button", + // // id: this.ids.buttonCancel, + // // value: L("Cancel"), + // // css: "ab-cancel-button", + // // autowidth: true, + // // click: () => { + // // this.cancel(); + // // }, + // // }, + // { + // view: "button", + // id: this.ids.buttonVerify, + // css: "webix_primary", + // value: L("Verify"), + // autowidth: true, + // type: "form", + // click: () => { + // return this.verify(); + // }, + // }, + // ], + // }, + ], + }, + }; + } + + async init(AB) { + this.AB = AB; + + this.$form = $$(this.ids.form); + this.$list = $$(this.ids.tableList); + // this.$fieldSelector = $$(this.ids.fieldSelector); + AB.Webix.extend(this.$form, webix.ProgressBar); + AB.Webix.extend(this.$list, webix.ProgressBar); + // AB.Webix.extend(this.$fieldSelector, webix.ProgressBar); + + // init() routines are always considered async so: + return Promise.resolve(); + } + + disable() { + $$(this.ids.form).disable(); + } + + enable() { + $$(this.ids.form).enable(); + } + + formClear() { + this.$form.clearValidation(); + this.$form.clear(); + + $$(this.ids.searchText).setValue(""); + this.$list.filter(() => true); + this.lastSelectedTable = null; + } + getValues() { + return this.lastSelectedTable; + } + + async loadCatalog() { + this.busy(); + let result = await this.AB.Network.get({ + url: "/netsuite/metadata", + params: { credentials: JSON.stringify(this.credentials) }, + }); + + let data = []; + result.forEach((r) => { + data.push({ id: r, value: r }); + }); + let $table = $$(this.ids.tableList); + $table.clearAll(); + $table.parse(data); + + // console.error(data); + this.emit("tables", data); + } + + _fieldItem(def) { + const self = this; + const fieldTypes = this.AB.Class.ABFieldManager.allFields(); + const fieldKeys = ["string", "LongText", "number", "date", "boolean"]; + + let key = def.column || def.title; + let type = def.type; + + return { + cols: [ + { + view: "text", + value: key, + placeholder: "key", + }, + { + placeholder: "Type", + options: fieldKeys.map((fKey) => { + return { + id: fKey, + value: fieldTypes + .filter((f) => f.defaults().key == fKey)[0] + ?.defaults().menuName, + }; + }), + view: "select", + value: type, + }, + { + icon: "wxi-trash", + view: "icon", + width: 38, + click: function () { + const $item = this.getParentView(); + $$(self.ids.fields).removeView($item); + }, + }, + ], + }; + } + + async loadFields(table) { + // $$(this.ids.fieldSelector).show(); + this.busyFields(); + + let result = await this.AB.Network.get({ + url: `/netsuite/table/${table}`, + params: { credentials: JSON.stringify(this.credentials) }, + }); + + this.fieldList = result; + (result || []).forEach((f) => { + const uiItem = this._fieldItem(f); + $$(this.ids.fields).addView(uiItem); + }); + this.readyFields(); + } + + /** + * @method onError() + * Our Error handler when the data we provided our parent + * ui_work_object_list_newObject object had an error saving + * the values. + * @param {Error|ABValidation|other} err + * The error information returned. This can be several + * different types of objects: + * - A javascript Error() object + * - An ABValidation object returned from our .isValid() + * method + * - An error response from our API call. + */ + onError(err) { + if (err) { + console.error(err); + let message = L("the entered data is invalid"); + // if this was our Validation() object: + if (err.updateForm) { + err.updateForm(this.$form); + } else { + if (err.code && err.data) { + message = err.data?.sqlMessage ?? message; + } else { + message = err?.message ?? message; + } + } + + const values = this.$form.getValues(); + webix.alert({ + title: L("Error creating Object: {0}", [values.name]), + ok: L("fix it"), + text: message, + type: "alert-error", + }); + } + // get notified if there was an error saving. + $$(this.ids.buttonVerify).enable(); + } + + /** + * @method onSuccess() + * Our success handler when the data we provided our parent + * ui_work_object_list_newObject successfully saved the values. + */ + onSuccess() { + this.formClear(); + $$(this.ids.buttonVerify).enable(); + } + + /** + * @function show() + * + * Show this component. + */ + show() { + $$(this.ids.component)?.show(); + } + + busy() { + const $list = $$(this.ids.tableList); + // const $verifyButton = $$(this.ids.buttonVerify); + + $list.showProgress({ type: "icon" }); + // $verifyButton.disable(); + } + + // busyFields() { + // this.$fieldSelector.showProgress({ type: "icon" }); + // } + + ready() { + const $form = $$(this.ids.form); + // const $verifyButton = $$(this.ids.buttonVerify); + + $form.hideProgress(); + // $verifyButton.enable(); + } + + // readyFields() { + // this.$fieldSelector.hideProgress(); + // } + + setCredentials(creds) { + this.credentials = creds; + } + } + return new UI_Work_Object_List_NewObject_Netsuite_Tables(); +} diff --git a/src/rootPages/Designer/ui_work_object_workspace.js b/src/rootPages/Designer/ui_work_object_workspace.js index 94527dd0..9513c00c 100644 --- a/src/rootPages/Designer/ui_work_object_workspace.js +++ b/src/rootPages/Designer/ui_work_object_workspace.js @@ -179,15 +179,18 @@ export default function (AB, ibase, init_settings) { this.callbackHeaderEditorMenu(action, field, node); }); - if (!this.isReadOnly) { - this.PopupDefineLabelComponent = new FPopupDefineLabel( - AB, - `${base}_defineLabel` - ); - this.PopupDefineLabelComponent.on("changed", () => { - this.callbackDefineLabel(); - }); - } + // NOTE: label information is only concerning how we display the info + // from this Object. It doesn't impact the data. So we can go ahead + // and allow users to modify the Label data. + // if (!this.isReadOnly) { + this.PopupDefineLabelComponent = new FPopupDefineLabel( + AB, + `${base}_defineLabel` + ); + this.PopupDefineLabelComponent.on("changed", () => { + this.callbackDefineLabel(); + }); + // } this.PopupFilterDataTableComponent = new FPopupFilterDataTable( AB, @@ -437,7 +440,7 @@ export default function (AB, ibase, init_settings) { icon: "fa fa-crosshairs", css: "webix_transparent", type: "icon", - hidden: this.isReadOnly, + // hidden: this.isReadOnly, click: function () { _logic.toolbarDefineLabel(this.$view); }, @@ -707,9 +710,9 @@ export default function (AB, ibase, init_settings) { allInits.push(this.PopupHeaderEditMenu.init(AB)); - if (!this.isReadOnly) { - allInits.push(this.PopupDefineLabelComponent.init(AB)); - } + // if (!this.isReadOnly) { + allInits.push(this.PopupDefineLabelComponent.init(AB)); + // } allInits.push(this.PopupFilterDataTableComponent.init(AB)); this.PopupFilterDataTableComponent.on("save", (...params) => { @@ -1391,8 +1394,8 @@ export default function (AB, ibase, init_settings) { if (!this.isReadOnly) { this.PopupNewDataFieldComponent.objectLoad(object); - this.PopupDefineLabelComponent.objectLoad(object); } + this.PopupDefineLabelComponent.objectLoad(object); this.PopupFilterDataTableComponent.objectLoad(object); this.PopupFrozenColumnsComponent.objectLoad(object); @@ -1782,7 +1785,7 @@ export default function (AB, ibase, init_settings) { const ids = this.ids; const $buttonAddField = $$(ids.buttonAddField), $spaceAddField = $$(ids.spaceAddField), - $buttonLabel = $$(ids.buttonLabel), + // $buttonLabel = $$(ids.buttonLabel), $spaceLabel = $$(ids.spaceLabel), $buttonImport = $$(ids.buttonImport), $spaceImport = $$(ids.spaceImport), @@ -1792,7 +1795,7 @@ export default function (AB, ibase, init_settings) { if (this._isReadOnly) { $buttonAddField?.hide(); $spaceAddField?.hide(); - $buttonLabel?.hide(); + // $buttonLabel?.hide(); $spaceLabel?.hide(); $buttonImport?.hide(); $spaceImport?.hide(); @@ -1801,7 +1804,7 @@ export default function (AB, ibase, init_settings) { } else { $buttonAddField?.show(); $spaceAddField?.show(); - $buttonLabel?.show(); + // $buttonLabel?.show(); $spaceLabel?.show(); $buttonImport?.show(); $spaceImport?.show(); diff --git a/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js b/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js index 46a42248..ec83732e 100644 --- a/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js +++ b/src/rootPages/Designer/ui_work_object_workspace_popupNewDataField.js @@ -410,6 +410,12 @@ export default function (AB, ibase) { let linkCol; + if (this.CurrentObject.isNetsuite) { + if (vals.settings?.netsuiteOneColumn) { + vals.columnName = vals.settings.netsuiteOneColumn; + } + } + // if this is an ADD operation, (_editField will be undefined) if (!this._editField) { // get a new instance of a field: @@ -420,7 +426,9 @@ export default function (AB, ibase) { field.columnName === "" ) { this.AB.Webix.message({ - text: "The column name can't contain special characters.", + text: L( + "The column name can't contain special characters." + ), type: "error", }); @@ -577,7 +585,10 @@ export default function (AB, ibase) { await field.save(); // when add new link fields, then run create migrate fields here - if (!this._editField) { + if ( + !this._editField && + !this.CurrentObject.isNetsuite + ) { await field.migrateCreate(); await linkCol.migrateCreate(); }