+ `;
+ return table;
+}
+// Highlights cells red for any cell with invalid data
+function updateTableColors(itemindex, category) {
+ const colLoc = {
+ description: -1,
+ uom: -1,
+ commGroup: -1,
+ glClass: -1,
+ maximo: -1,
+ vendor: -1,
+ storeroom: -1,
+ catNum: -1,
+ siteID: -1,
+ };
+ const table = document.getElementById('batch-items-table');
+ // Assign column locations
+ const cols = parseInt(table.getAttribute('data-cols'));
+ // go through first row to find headings.
+ for (let i = 1; i <= cols; i++) {
+ // get a cell in the table by its id
+ const cell = document.getElementById('1-' + i);
+ // see if cell value matches any of the required parameters to create an item object
+ if (cell.innerHTML.toUpperCase() === 'DESCRIPTION') {
+ colLoc.description = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'UOM' || cell.innerHTML.toUpperCase() === 'ISSUE UNIT') {
+ colLoc.uom = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'COMMODITY GROUP' || cell.innerHTML.toUpperCase() === 'COMM GROUP') {
+ colLoc.commGroup = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'GL CLASS') {
+ colLoc.glClass = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'SITEID' || cell.innerHTML.toUpperCase() === 'SITE') {
+ colLoc.siteID = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'STOREROOM' || cell.innerHTML.toUpperCase() === 'STOREROOM') {
+ colLoc.storeroom = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'VENDOR' || cell.innerHTML.toUpperCase() === 'VENDOR NUMBER') {
+ colLoc.vendor = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'CAT NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOG NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOGUE NUMBER') {
+ colLoc.catNum = i;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'MAXIMO' || cell.innerHTML.toUpperCase() === 'ITEM NUMBER') {
+ colLoc.maximo = i;
+ }
+ }
+ const colNum = colLoc[category];
+ const cell = document.getElementById(`${itemindex}-${colNum}`);
+ // change color of cell
+ cell.classList.add('table-danger');
+}
+function updateItemStatus(status, itemindex) {
+ // Changes item status column to reflect status
+ const statusimg = document.getElementById(`item-${itemindex}-status`);
+ if (status == 'fail') {
+ statusimg.innerHTML = `close`;
+ }
+ else if (status == 'success') {
+ statusimg.innerHTML = `done`;
+ }
+ else if (status == 'loading') {
+ statusimg.innerHTML = ``;
+ }
+ else if (status == 'error') {
+ statusimg.innerHTML = `error`;
+ }
+ else if (status == 'partial') {
+ statusimg.innerHTML = `warning`;
+ }
+ else {
+ statusimg.innerHTML = `pending`;
+ }
+}
+function fileBase64(file) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = function () {
+ resolve(reader.result);
+ };
+ reader.onerror = function (error) {
+ reject(error);
+ };
+ });
+}
diff --git a/built/misc/constants.js b/built/misc/constants.js
new file mode 100644
index 0000000..9b9a45f
--- /dev/null
+++ b/built/misc/constants.js
@@ -0,0 +1,7 @@
+"use strict";
+//for global constants. used for debugging
+const CONSTANTS = Object.freeze({
+ ENV: true ? 'prod.manage.prod' : 'test.manage.test', //set true to use production environment and set false for test environment.
+ OPEN_DEV_TOOLS: true, //set true to open dev tools on application launch
+});
+module.exports = CONSTANTS;
diff --git a/built/misc/exceljs.js b/built/misc/exceljs.js
new file mode 100644
index 0000000..978e81a
--- /dev/null
+++ b/built/misc/exceljs.js
@@ -0,0 +1,369 @@
+"use strict";
+// don't use
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+const Exceljs = require('exceljs');
+class Spreadsheet {
+ constructor(filePath) {
+ this.filePath = filePath;
+ }
+ getDescriptions(params) {
+ return __awaiter(this, void 0, void 0, function* () {
+ // returns all the english descriptions on a workbook
+ // {wsname:string, maxNumCol:string, description:[string], manufacturerer: string, startingRow: int}
+ const wb = new Exceljs.Workbook();
+ yield wb.xlsx.readFile(this.filePath);
+ const ws = wb.getWorksheet(params.wsname);
+ const lastRow = ws.lastRow.number;
+ let descriptions = [];
+ let description = "";
+ for (let i = params.startingRow; i <= lastRow; i++) {
+ for (const col of params.descriptions) {
+ description = `${description},${ws.getCell(`${col}${i}`).text}`;
+ }
+ descriptions.push({
+ maxNum: ws.getCell(`${params.maxNumCol}${i}`).text,
+ description: description,
+ manufacturer: ws.getCell(`${params.manufacturerer}${i}`).text
+ });
+ description = "";
+ }
+ return descriptions;
+ });
+ }
+ getTranslations() {
+ return __awaiter(this, void 0, void 0, function* () {
+ // read all translation definitions from a workbook for import
+ const wb = new Exceljs.Workbook();
+ yield wb.xlsx.readFile(this.filePath);
+ const ws = wb.worksheets[0]; //assume there is only 1 worksheet and its the one we want
+ const lastRow = ws.lastRow.number;
+ const languages = ws.getRow(1).cellCount;
+ let lang_codes = [];
+ for (let i = 2; i <= languages; i++) {
+ lang_codes.push(ws.getCell(1, i).text.toUpperCase());
+ }
+ let translations = [];
+ for (let i = 2; i <= lastRow; i++) {
+ for (let j = 2; j <= languages; j++) {
+ translations.push({
+ english: ws.getCell(i, 1).text.toUpperCase().trim(),
+ lang_code: lang_codes[j - 2],
+ translation: ws.getCell(i, j).text.toUpperCase().trim()
+ });
+ }
+ }
+ return translations;
+ });
+ }
+ saveTranslations(data) {
+ return __awaiter(this, void 0, void 0, function* () {
+ // save translated descriptions to excel file
+ // multiple worksheets one for each language, and one for missing words
+ const wb = new Exceljs.Workbook();
+ let ws;
+ let row;
+ let rowCount;
+ for (const lang of data.langs) {
+ rowCount = 2;
+ ws = wb.addWorksheet(lang);
+ row = ws.getRow(1);
+ row.values = ['Maximo Item Number', 'English Description', `${lang} Description`, 'Manufacturer Name'];
+ for (const item of data.item) {
+ row = ws.getRow(rowCount);
+ row.values = [item.maxNum, item.description, item[lang], item.manufacturer || "-"];
+ rowCount++;
+ }
+ }
+ if (data.missing) {
+ ws = wb.addWorksheet('Missing Translations');
+ rowCount = 1;
+ for (const change of data.missing) {
+ row = ws.getRow(rowCount);
+ row.values = [change];
+ rowCount++;
+ }
+ }
+ yield wb.xlsx.writeFile(this.filePath);
+ postMessage(['result', 'done']);
+ });
+ }
+ saveObserListChanges(data) {
+ return __awaiter(this, void 0, void 0, function* () {
+ // expected attributes for data: domain.changes, domain.delete
+ // saves observation list changes data, expect data in object format
+ const wb = new Exceljs.Workbook();
+ let ws;
+ let row;
+ let rowCount;
+ if (data.domainDef.changes) {
+ rowCount = 3;
+ ws = wb.addWorksheet('ChangeCondDomDef');
+ row = ws.getRow(1);
+ row.values = ['IKO_Import', 'IKO_ALNDOMAIN', 'AddChange', 'EN'];
+ row = ws.getRow(2);
+ row.values = ['DOMAINID', 'DESCRIPTION', 'DOMAINTYPE', 'MAXTYPE', 'LENGTH'];
+ for (const change of data.domainDef.changes) {
+ row = ws.getRow(rowCount);
+ row.values = [change.list_id, change.inspect, 'ALN', 'UPPER', '3'];
+ rowCount++;
+ }
+ }
+ if (data.domainDef.delete) {
+ ws = wb.addWorksheet('RemoveCondDomDef');
+ row = ws.getRow(1);
+ row.values = ['DOMAINID'];
+ rowCount = 2;
+ for (const change of data.domainDef.delete) {
+ row = ws.getRow(rowCount);
+ row.values = [change];
+ rowCount++;
+ }
+ }
+ if (data.domainVal.changes) {
+ let rowCount = 3;
+ ws = wb.addWorksheet('ChangeCondDomVal');
+ row = ws.getRow(1);
+ row.values = ['IKO_Import', 'IKO_ALNDOMAIN', 'AddChange', 'EN'];
+ row = ws.getRow(2);
+ row.values = ['DOMAINID', 'VALUE', 'AD_DESCRIPTION'];
+ for (const observ of data.domainVal.changes) {
+ row = ws.getRow(rowCount);
+ row.values = [observ.meter, observ.id_value, observ.observation];
+ rowCount++;
+ }
+ }
+ if (data.domainVal.delete) {
+ ws = wb.addWorksheet('RemoveCondDomVal');
+ row = ws.getRow(1);
+ row.values = ['DOMAINID:VALUE'];
+ rowCount = 2;
+ for (const change of data.domainVal.delete) {
+ row = ws.getRow(rowCount);
+ row.values = [change];
+ rowCount++;
+ }
+ }
+ if (data.meter.changes) {
+ let rowCount = 3;
+ ws = wb.addWorksheet('ChangeCondMeter');
+ row = ws.getRow(1);
+ row.values = ['IKO_Import', 'IKO_METER', 'AddChange', 'EN'];
+ row = ws.getRow(2);
+ row.values = ['METERNAME', 'DESCRIPTION', 'METERTYPE', 'DOMAINID'];
+ for (const observ of data.meter.changes) {
+ row = ws.getRow(rowCount);
+ row.values = [`${observ.list_id.slice(2)}01`, observ.inspect, 'CHARACTERISTIC', observ.list_id];
+ rowCount++;
+ }
+ }
+ if (data.meter.delete) {
+ ws = wb.addWorksheet('RemoveCondMeter');
+ row = ws.getRow(1);
+ row.values = ['DOMAINID'];
+ rowCount = 2;
+ for (const change of data.meter.delete) {
+ row = ws.getRow(rowCount);
+ row.values = [change];
+ rowCount++;
+ }
+ }
+ if (data.jobTask.changes) {
+ let rowCount = 3;
+ ws = wb.addWorksheet('ChangeJobTask');
+ row = ws.getRow(1);
+ row.values = ['IKO_Import', 'IKO_JOBTASK', 'AddChange', 'EN'];
+ row = ws.getRow(2);
+ row.values = [
+ 'ORGID', 'SITEID', 'JPNUM', 'PLUSCREVNUM', 'JPTASK',
+ 'PLUSCJPREVNUM', 'METERNAME', 'DESCRIPTION',
+ 'DESCRIPTION_LONGDESCRIPTION', '', 'OLD DESCRIPTION', 'OLD EXTENDED DESCRIPTION'
+ ];
+ for (const jobTask of data.jobTask.changes) {
+ row = ws.getRow(rowCount);
+ row.values = [
+ jobTask.orgid, jobTask.siteid, jobTask.jpnum,
+ 0, jobTask.jptask, 0, jobTask.metername, jobTask.desc,
+ jobTask.ext_desc, '', jobTask.old_desc, jobTask.old_ext_desc
+ ];
+ rowCount++;
+ }
+ }
+ if (data.jobTask.delete) {
+ ws = wb.addWorksheet('RemoveJobTask');
+ row = ws.getRow(1);
+ row.values = [
+ 'ORGID', 'SITEID', 'JPNUM', 'PLUSCREVNUM', 'JPTASK',
+ 'PLUSCJPREVNUM', 'METERNAME', 'DESCRIPTION',
+ 'DESCRIPTION_LONGDESCRIPTION'
+ ];
+ rowCount = 2;
+ for (const jobTask of data.jobTask.delete) {
+ row = ws.getRow(rowCount);
+ row.values = [
+ jobTask.orgid, jobTask.siteid, jobTask.jpnum,
+ 0, jobTask.jptask, 0, jobTask.metername, jobTask.desc,
+ jobTask.ext_desc
+ ];
+ rowCount++;
+ }
+ }
+ yield wb.xlsx.writeFile(this.filePath);
+ postMessage(['result', 'done']);
+ });
+ }
+ getJobTasks() {
+ return __awaiter(this, void 0, void 0, function* () {
+ // getting extended description from REST API takes way too long, so require a worksheet with information retrived from the DB
+ // select jpnum, jptask, description, orgid, siteid, metername, ldtext from
+ // (select jpnum, jptask, description, orgid, siteid, metername, jobtaskid from jobtask where metername is not null) as t1
+ // left join
+ // (select ldtext, ldkey from longdescription where ldownertable = 'JOBTASK') as t2
+ // on t1.jobtaskid = t2.ldkey
+ const wb = new Exceljs.Workbook();
+ yield wb.xlsx.readFile(this.filePath);
+ const ws = wb.worksheets[0]; //assume there is only 1 worksheet and its the one we want
+ const lastRow = ws.lastRow.number;
+ let row = ws.getRow(1).values.slice(1);
+ let jobTasks = [];
+ if (row.equals(['jpnum', 'jptask', 'description', 'orgid', 'siteid', 'metername', 'ldtext'])) {
+ console.log('pass');
+ }
+ else {
+ postMessage(['error', `Please Check Column Heading for JobTask Input expecting ${['jpnum', 'jptask', 'description', 'orgid', 'siteid', 'metername', 'ldtext']}`]);
+ }
+ for (let i = 2; i <= lastRow; i++) {
+ row = ws.getRow(i).values;
+ jobTasks.push({
+ jpnum: row[1],
+ metername: row[6],
+ orgid: row[4],
+ siteid: row[3],
+ jptask: row[2],
+ desc: row[3],
+ ext_desc: row[7]
+ });
+ }
+ return jobTasks;
+ });
+ }
+ readObservList(wsname) {
+ return __awaiter(this, void 0, void 0, function* () {
+ // loop through the excel sheet to extract information
+ const wb = new Exceljs.Workbook();
+ yield wb.xlsx.readFile(this.filePath);
+ const ws = wb.getWorksheet(wsname);
+ let meter = '';
+ let meters = [];
+ let observation = {};
+ let observations = [];
+ let inspect;
+ let desc;
+ let temp1;
+ let temp2;
+ let temp3;
+ ws.eachRow(function (row, rowNumber) {
+ if (row.values[1] !== undefined) {
+ // check if row has meter definitions
+ meter = removeRichText(row.values[1]);
+ if (meters[meter]) {
+ postMessage(['error', `Duplicate Meter: ${meter} on Row: ${rowNumber}`]);
+ }
+ else {
+ inspect = removeRichText(row.values[3]);
+ desc = removeRichText(row.values[5]);
+ meters.push({
+ name: meter,
+ list_id: `M-${meter}`,
+ inspect: inspect,
+ desc: `Inspect ${inspect}`,
+ ext_desc: desc,
+ search_str: `M-${meter}~${inspect}`
+ });
+ temp1 = removeRichText(row.values[6]);
+ temp2 = removeRichText(row.values[7]);
+ temp3 = removeRichText(row.values[8]);
+ observation = {
+ meter: meter,
+ id_value: temp1,
+ observation: temp2,
+ action: temp3,
+ search_str: `${meter}~${temp1}`
+ };
+ observations.push(observation);
+ }
+ }
+ else if (row.values[6] !== undefined) {
+ // it is just a observation row
+ temp1 = removeRichText(row.values[6]);
+ temp2 = removeRichText(row.values[7]);
+ temp3 = removeRichText(row.values[8]);
+ observation = {
+ meter: meter,
+ id_value: temp1,
+ observation: temp2,
+ action: temp3,
+ search_str: `${meter}~${temp1}`
+ };
+ observations.push(observation);
+ }
+ });
+ postMessage(['result', [meters.slice(1), observations.slice(1)]]);
+ });
+ }
+}
+function removeRichText(value) {
+ // when reading cells from a row object there is no option for removing richtext
+ if (typeof (value) === "string") {
+ return value;
+ }
+ else if (typeof (value) == "object") {
+ let temp = "";
+ for (const part of value.richText)
+ temp = `${temp}${part.text}`;
+ return temp;
+ }
+ else if (typeof (value) == "undefined") {
+ return undefined;
+ }
+ else {
+ postMessage(['error', `Unknown cell type: ${typeof (value)}`]);
+ }
+}
+//https://stackoverflow.com/a/14853974
+// Warn if overriding existing method
+// Check if two arrays are equal
+if (Array.prototype.equals)
+ console.warn("Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.");
+// attach the .equals method to Array's prototype to call it on any array
+Array.prototype.equals = function (array) {
+ // if the other array is a falsy value, return
+ if (!array)
+ return false;
+ // compare lengths - can save a lot of time
+ if (this.length != array.length)
+ return false;
+ for (var i = 0, l = this.length; i < l; i++) {
+ // Check if we have nested arrays
+ if (this[i] instanceof Array && array[i] instanceof Array) {
+ // recurse into the nested arrays
+ if (!this[i].equals(array[i]))
+ return false;
+ }
+ else if (this[i] != array[i]) {
+ // Warning - two different object instances will never be equal: {x:20} != {x:20}
+ return false;
+ }
+ }
+ return true;
+};
+// Hide method from for-in loops
+Object.defineProperty(Array.prototype, "equals", { enumerable: false });
+module.exports = Spreadsheet;
diff --git a/built/misc/indexDB.js b/built/misc/indexDB.js
new file mode 100644
index 0000000..da6999e
--- /dev/null
+++ b/built/misc/indexDB.js
@@ -0,0 +1,508 @@
+"use strict";
+const Sql = require('better-sqlite3');
+const utils = require('./utils');
+const intersection = require('lodash/intersection');
+// https://lodash.com/docs/4.17.15#intersection
+// fast library for intersection of arrays
+/**
+ * Database Class containing more Database functions
+ */
+class Database {
+ /**
+ * Open default db file
+ */
+ constructor() {
+ this.db = new Sql(`${process.env.APPDATA}/EAM Spare Parts/program.db`); // , { verbose: console.log });
+ }
+ createTables() {
+ const dropTables = this.db.prepare('DROP TABLE IF EXISTS manufacturers');
+ const dropTables2 = this.db.prepare('DROP TABLE IF EXISTS abbreviations');
+ const dropTables3 = this.db.prepare('DROP TABLE IF EXISTS workingDescription');
+ const dropTables4 = this.db.prepare('DROP TABLE IF EXISTS itemCache');
+ const dropTables5 = this.db.prepare('DROP TABLE IF EXISTS itemDescAnalysis');
+ const dropTables6 = this.db.prepare('DROP TABLE IF EXISTS inventoryCache');
+ const runQuery2 = this.db.transaction(() => {
+ dropTables.run();
+ dropTables2.run();
+ dropTables3.run();
+ dropTables4.run();
+ dropTables5.run();
+ dropTables6.run();
+ });
+ runQuery2();
+ const createTable1 = this.db.prepare(`CREATE TABLE manufacturers(
+ id INTEGER PRIMARY KEY,
+ full_name TEXT NOT NULL COLLATE NOCASE,
+ short_name TEXT NOT NULL UNIQUE COLLATE NOCASE,
+ homepage TEXT,
+ changed_date TEXT COLLATE NOCASE
+ );`);
+ const createTable2 = this.db.prepare(`CREATE TABLE abbreviations(
+ id INTEGER PRIMARY KEY,
+ orig_text TEXT NOT NULL COLLATE NOCASE,
+ replace_text TEXT NOT NULL COLLATE NOCASE
+ )`);
+ const createTable3 = this.db.prepare(`CREATE TABLE workingDescription (
+ row INTEGER NOT NULL,
+ description TEXT NOT NULL COLLATE NOCASE,
+ analysis TEXT,
+ related TEXT,
+ translate TEXT,
+ orgid TEXT COLLATE NOCASE
+ )`);
+ const createTable4 = this.db.prepare(`CREATE TABLE itemCache (
+ itemnum TEXT PRIMARY KEY,
+ description TEXT NOT NULL COLLATE NOCASE,
+ details TEXT COLLATE NOCASE,
+ changed_date TEXT COLLATE NOCASE,
+ search_text TEXT COLLATE NOCASE,
+ gl_class TEXT COLLATE NOCASE,
+ uom TEXT COLLATE NOCASE,
+ commodity_group TEXT COLLATE NOCASE,
+ ext_search_text TEXT COLLATE NOCASE,
+ ext_description TEXT COLLATE NOCASE
+ )`);
+ const createTable5 = this.db.prepare(`CREATE TABLE itemDescAnalysis (
+ tree TEXT PRIMARY KEY COLLATE NOCASE,
+ descriptor TEXT NOT NULL COLLATE NOCASE,
+ parent TEXT,
+ count INTEGER,
+ level INTEGER
+ )`);
+ const createTable6 = this.db.prepare(`CREATE TABLE inventoryCache (
+ itemnum TEXT NOT NULL COLLATE NOCASE,
+ siteid TEXT NOT NULL COLLATE NOCASE,
+ catalogcode TEXT COLLATE NOCASE,
+ modelnum TEXT COLLATE NOCASE,
+ vendor TEXT COLLATE NOCASE,
+ manufacturer TEXT COLLATE NOCASE,
+ companyname TEXT COLLATE NOCASE,
+ rowstamp TEXT,
+ location TEXT NOT NULL COLLATE NOCASE,
+ PRIMARY KEY (itemnum, location)
+ )`);
+ const runQuery = this.db.transaction(() => {
+ createTable1.run();
+ createTable2.run();
+ createTable3.run();
+ createTable4.run();
+ createTable5.run();
+ createTable6.run();
+ });
+ runQuery();
+ console.log('refreshed tables');
+ }
+ clearItemCache() {
+ const stmt = this.db.prepare(`DELETE FROM itemCache`);
+ stmt.run();
+ }
+ saveInventoryCache(data) {
+ const dataDB = [];
+ for (let i = 0; i < data.length; i++) {
+ dataDB.push({
+ itemnum: data[i][0],
+ siteid: data[i][1],
+ catalogcode: data[i][2],
+ modelnum: data[i][3],
+ vendor: data[i][4],
+ manufacturer: data[i][5],
+ companyname: data[i][6],
+ rowstamp: data[i][8],
+ location: data[i][7],
+ });
+ }
+ const insert = this.db.prepare(`insert or replace into inventoryCache (
+ itemnum, siteid, catalogcode, modelnum, vendor, manufacturer, companyname, rowstamp, location)
+ VALUES (@itemnum, @siteid, @catalogcode, @modelnum, @vendor, @manufacturer, @companyname, @rowstamp, @location)`);
+ const insertMany = this.db.transaction((dataDB) => {
+ for (const item of dataDB)
+ insert.run(item);
+ });
+ insertMany(dataDB);
+ }
+ /**
+ * For Updated items obtained from Maximo.
+ * Update the extended search information
+ * @param {list} itemNums Data returned from Maximo
+ * @return {list} processed data
+ */
+ processNewItems(itemNums) {
+ let stmt;
+ const updatedData = [];
+ const inventoryData = new Map();
+ for (const [key, value] of itemNums) {
+ stmt = this.db.prepare(`SELECT * FROM itemCache WHERE itemnum = '${value}'`);
+ const itemDetails = stmt.get();
+ stmt = this.db.prepare(`SELECT * FROM inventoryCache WHERE itemnum = '${key}'`);
+ const results = stmt.all();
+ results.forEach((inventory) => {
+ const row = [];
+ row[0] = inventory.itemnum;
+ row[1] = inventory.siteid;
+ row[2] = inventory.catalogcode;
+ row[3] = inventory.modelnum;
+ row[4] = inventory.vendor;
+ row[5] = inventory.manufacturer;
+ row[6] = inventory.companyname;
+ row[7] = inventory.location;
+ if (inventoryData.has(inventory.itemnum)) {
+ for (let j = 1; j <= 7; j++) {
+ if (row[j] != '') {
+ inventoryData.get(inventory.itemnum)[j] = inventoryData.get(inventory.itemnum)[j] + '|' + row[j];
+ }
+ }
+ }
+ else {
+ inventoryData.set(inventory.itemnum, row);
+ }
+ });
+ updatedData.push([
+ itemDetails.itemnum,
+ itemDetails.description,
+ itemDetails.changed_date,
+ itemDetails.gl_class,
+ itemDetails.uom,
+ itemDetails.commodity_group,
+ itemDetails.details,
+ inventoryData.get(key),
+ ]);
+ }
+ return updatedData;
+ }
+ /**
+ * save items into db
+ * @param {list} data from source
+ */
+ saveItemCache(data) {
+ const dataDB = [];
+ let search = '';
+ let extSearch = '';
+ let extDesc = '';
+ for (let i = 0; i < data.length; i++) {
+ if (data[i][1]) { // test if description is blank
+ search = data[i][1].toUpperCase();
+ for (const char of utils.STRINGCLEANUP) {
+ search = search.replaceAll(char, '');
+ }
+ extSearch = search;
+ extDesc = '';
+ // add inventory data
+ if (data[i][7]) {
+ for (let j = 0; j < data[i][7].length; j++) {
+ if (data[i][7][j].length > 0) {
+ extSearch = `${extSearch}|${data[i][7][j]}`;
+ }
+ }
+ for (let j = 2; j < data[i][7].length; j++) {
+ if (data[i][7][j].length > 0) {
+ extDesc = `${extDesc}${data[i][7][j].replaceAll('|', ' ')} `;
+ }
+ }
+ }
+ // add item master details
+ if (data[i][6]) {
+ extSearch = `${extSearch}|${data[i][6]}`;
+ extDesc = `${extDesc}${data[i][6]}`;
+ for (const char of utils.STRINGCLEANUP) {
+ extSearch = extSearch.replaceAll(char, '');
+ }
+ }
+ dataDB.push({
+ itemnum: data[i][0],
+ description: data[i][1],
+ changed_date: data[i][2],
+ gl_class: data[i][3],
+ uom: data[i][4],
+ commodity_group: data[i][5],
+ details: data[i][6],
+ search_text: search,
+ ext_search_text: extSearch,
+ ext_desc: extDesc,
+ });
+ }
+ }
+ const insert = this.db.prepare(`INSERT OR REPLACE INTO itemCache (
+ itemnum, description, details, changed_date, search_text, gl_class, uom, commodity_group, ext_search_text, ext_description)
+ VALUES (@itemnum, @description, @details, @changed_date, @search_text, @gl_class, @uom, @commodity_group, @ext_search_text, @ext_desc)`);
+ const insertMany = this.db.transaction((dataDB) => {
+ for (const item of dataDB)
+ insert.run(item);
+ });
+ insertMany(dataDB);
+ const manufs = this.getAllManufacturers();
+ const analysis = itemOccurrence(data, manufs);
+ const stmt = this.db.prepare(`INSERT INTO itemDescAnalysis (tree, descriptor, parent, count, level)
+ VALUES (@tree, @descriptor, @parent, @count, @level)
+ ON CONFLICT(tree)
+ DO UPDATE SET count = count + @count`);
+ const insertMany2 = this.db.transaction((analysis) => {
+ for (const [key, item] of analysis) {
+ stmt.run({ tree: key, descriptor: item.phrase, parent: item.parent, count: item.count, level: item.level });
+ }
+ });
+ insertMany2(analysis);
+ console.log('finished adding analysis');
+ }
+ getAnalysis(tree) {
+ const stmt = this.db.prepare('SELECT tree, descriptor, parent, count, level FROM itemDescAnalysis where tree = @tree');
+ const result = stmt.get({ tree: tree });
+ return result;
+ }
+ getAllWorkingDesc() {
+ const stmt = this.db.prepare('SELECT *, itemCache.description FROM workingDescription left join itemCache on itemCache.itemnum = workingDescription.related WHERE analysis IS NOT NULL');
+ const result = stmt.all();
+ return result;
+ }
+ // get the time stamp (version) of when the item cache was last updated
+ getVersion(type) {
+ let stmt;
+ if (type === 'maximo') {
+ stmt = this.db.prepare('SELECT changed_date FROM itemCache ORDER BY changed_date DESC LIMIT 1');
+ }
+ else if (type === 'manufacturer') {
+ stmt = this.db.prepare('SELECT changed_date FROM manufacturers ORDER BY changed_date DESC LIMIT 1');
+ }
+ else if (type === 'inventory') {
+ stmt = this.db.prepare('SELECT rowstamp FROM inventoryCache ORDER BY rowstamp DESC LIMIT 1');
+ }
+ let version = stmt.all();
+ if (version.length == 0) {
+ version = [{ changed_date: '2022-01-01 00:00:00' }];
+ }
+ return version;
+ }
+ getAllManufacturers() {
+ const stmt = this.db.prepare('SELECT short_name FROM manufacturers');
+ return stmt.all();
+ }
+ // populate the database with abbrivations
+ populateAbbr(data) {
+ const dataDB = [];
+ for (let i = 0; i < data.length; i++) {
+ dataDB.push({ orig_text: data[i][0], replace_text: data[i][1] });
+ }
+ const insert = this.db.prepare(`INSERT INTO abbreviations (
+ orig_text, replace_text)
+ VALUES (@orig_text, @replace_text)`);
+ const insertMany = this.db.transaction((data) => {
+ for (const item of data)
+ insert.run(item);
+ });
+ insertMany(dataDB);
+ }
+ // populate the database with manufacturers
+ saveManufacturers(data) {
+ const dataDB = [];
+ for (let i = 0; i < data.length; i++) {
+ dataDB.push({ full_name: data[i][2], short_name: data[i][0], homepage: data[i][3], changed_date: data[i][1] });
+ }
+ const insert = this.db.prepare(`INSERT OR REPLACE INTO manufacturers (
+ full_name, short_name, homepage, changed_date)
+ VALUES (@full_name, @short_name, @homepage, @changed_date)`);
+ const insertMany = this.db.transaction((data) => {
+ for (const item of data)
+ insert.run(item);
+ });
+ insertMany(dataDB);
+ }
+ // checks if the name given is a manufacturer
+ isManufacturer(name) {
+ const stmt = this.db.prepare(`SELECT short_name FROM manufacturers where full_name = ? or short_name = ?`);
+ return stmt.get([name, name]);
+ }
+ // check if the phrase given has a known abbrivation
+ isAbbreviation(phase) {
+ const result = this.db.prepare(`SELECT replace_text from abbreviations where orig_text = ?`);
+ return result.get([phase]);
+ }
+ // saves the current set of descriptions being worked on into the database
+ saveDescription(data) {
+ const dataDB = [];
+ for (const [, desc] of Object.entries(data)) {
+ dataDB.push({ row: desc[0], description: desc[1] });
+ }
+ console.log('starting to clear');
+ let stmt = this.db.prepare(`DELETE FROM workingDescription`);
+ stmt.run();
+ stmt = this.db.prepare(`INSERT INTO workingDescription (
+ row, description)
+ VALUES (@row, @description)`);
+ let count = 0;
+ const insertMany = this.db.transaction((dataDB) => {
+ for (const item of dataDB) {
+ if (item.description.length > 0) {
+ stmt.run(item);
+ count++;
+ }
+ }
+ });
+ insertMany(dataDB);
+ console.log('finished adding');
+ return count;
+ }
+ saveDescriptionAnalysis(data, row) {
+ const stmt = this.db.prepare('UPDATE workingDescription SET analysis = ?, related = ? WHERE row = ?');
+ stmt.run(JSON.stringify(data), data.related, row);
+ }
+ getDescription(row) {
+ const result = this.db.prepare(`SELECT * FROM workingDescription WHERE row >= '${row}' ORDER BY row ASC LIMIT 1`);
+ return result.get();
+ }
+ loadItem(itemnum) {
+ let result = this.db.prepare(`SELECT itemnum, description, gl_class, uom, commodity_group from itemCache where itemnum = '${itemnum}'`);
+ result = result.get();
+ return result;
+ }
+ findRelated(data, ext = false, postmessage) {
+ const itemDict = {};
+ for (const char of utils.STRINGCLEANUP) {
+ data = data.replaceAll(char, ',');
+ }
+ const phrases = data.split(',');
+ let result = [];
+ postMessage(['progress', 25, 'Getting Item Descriptions From Maximo']);
+ for (let i = 0; i < phrases.length; i++) {
+ if (phrases[i].length > 1) { // ignore single characters searches since they add too much time
+ result.push(this.fetchAndObjectify(phrases[i], ext, itemDict));
+ postMessage(['progress', 75, 'Processing Item Descriptions From Maximo']); // change this to per phrase
+ }
+ }
+ result = result.filter((item) => item !== false);
+ if (result.length) {
+ let arrayAsNum = [...Array(result.length).keys()]; // create an array with only integers to find combinations
+ arrayAsNum = getCombinations(arrayAsNum);
+ const intersections = [];
+ for (let i = arrayAsNum.length; i > 0; i--) { // convert combination of integers to combination of arrays
+ const holder = [];
+ arrayAsNum[i - 1].forEach((index) => {
+ holder.push(result[index]);
+ });
+ intersections.push([holder.length, intersection(...holder)]);
+ }
+ if (postmessage) {
+ postMessage(['result', matchAndScore(intersections), itemDict, data]);
+ }
+ else {
+ return [matchAndScore(intersections), itemDict, data];
+ }
+ }
+ else {
+ postMessage(['warning', 'No related items returned from Maximo']);
+ postMessage(['result', false]);
+ }
+ }
+ fetchAndObjectify(phrase, ext, itemDict) {
+ phrase = phrase.toUpperCase();
+ postMessage(['debug', `Getting item from cache: "${phrase}"`]);
+ let stmt;
+ if (ext) {
+ stmt = this.db.prepare(`SELECT * from itemCache where ext_search_text like ?`);
+ }
+ else {
+ stmt = this.db.prepare(`SELECT * from itemCache where search_text like ?`);
+ }
+ const result = stmt.all(`%${phrase}%`);
+ const itemNums = [];
+ result.forEach((item) => {
+ itemNums.push(item.itemnum);
+ if (ext) {
+ let desc = item.ext_search_text;
+ const idx = desc.indexOf('|'); // find the pipe symbol
+ // if there is a pipe symbol, then there is additional info after it
+ if (idx !== -1) {
+ let itemInfo = desc.slice(idx + 12); // stuff after the pipe symbol
+ itemInfo = itemInfo.replaceAll('NULL', '').replaceAll('|', ' ');
+ desc = item.description + '|' + itemInfo;
+ }
+ else { // no pipe symbol, just use the description
+ desc = item.description + '|';
+ }
+ itemDict[item.itemnum] = [desc, item.gl_class, item.uom, item.commodity_group];
+ }
+ else {
+ itemDict[item.itemnum] = [item.description, item.gl_class, item.uom, item.commodity_group];
+ }
+ });
+ return itemNums;
+ }
+}
+module.exports = Database;
+function matchAndScore(data) {
+ postMessage(['progress', 80, 'Processing Item Descriptions']);
+ const numPhases = data[0][0];
+ const matchedScores = {};
+ const saved = {};
+ data.forEach((item) => {
+ const score = item[0] / numPhases;
+ if (!(score in matchedScores)) {
+ matchedScores[score] = [];
+ }
+ item[1].forEach((itemNum) => {
+ if (!(itemNum in saved)) {
+ matchedScores[score].push(itemNum);
+ saved[itemNum] = 1;
+ }
+ });
+ });
+ return matchedScores;
+}
+// https://stackoverflow.com/a/59942031
+// Generate all possible non duplicate combinations of the arrays
+function getCombinations(valuesArray) {
+ const combi = [];
+ let temp = [];
+ const slent = Math.pow(2, valuesArray.length);
+ for (let i = 0; i < slent; i++) {
+ temp = [];
+ for (let j = 0; j < valuesArray.length; j++) {
+ if ((i & Math.pow(2, j))) {
+ temp.push(valuesArray[j]);
+ }
+ }
+ if (temp.length > 0) {
+ combi.push(temp);
+ }
+ }
+ combi.sort((a, b) => a.length - b.length);
+ return combi;
+}
+function itemOccurrence(data, manufs) {
+ const dataProcessed = new Map();
+ let description;
+ let level = 0;
+ let tree = '';
+ let parent = '';
+ const regex = /\d+/g;
+ for (let i = 0; i < data.length; i++) {
+ level = 0;
+ tree = '';
+ parent = '';
+ if (data[i][1]) { // test if description is blank
+ description = data[i][1].split(',');
+ for (let j = 0; j < description.length; j++) {
+ if (!(description[j].match(regex)) && !(manufs.includes(description[j]))) {
+ level++;
+ if (tree.length > 0) {
+ tree = tree + ',' + description[j];
+ }
+ else {
+ tree = description[j];
+ }
+ if (dataProcessed.has(tree)) {
+ dataProcessed.get(tree).count++;
+ }
+ else {
+ dataProcessed.set(tree, {
+ phrase: description[j],
+ parent: parent,
+ count: 1,
+ level: level,
+ });
+ }
+ parent = description[j];
+ }
+ }
+ }
+ }
+ return dataProcessed;
+}
diff --git a/built/misc/maximo.js b/built/misc/maximo.js
new file mode 100644
index 0000000..b4400a2
--- /dev/null
+++ b/built/misc/maximo.js
@@ -0,0 +1,472 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+// various functions for fetching data from maximo rest api
+const SharedDatabase = require('./sharedDB');
+const CONSTANTS = require('./constants.js');
+/**
+ * Class for all calls to Maximo
+ */
+class Maximo {
+ constructor() {
+ this.shareDB = new SharedDatabase();
+ this.login = this.shareDB.getPassword();
+ }
+ getMeters() {
+ return __awaiter(this, void 0, void 0, function* () {
+ let response;
+ let nextpage = true;
+ let pageno = 1;
+ const meters = [];
+ while (nextpage) {
+ try {
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/iko_meter?lean=1&pageno=${pageno}&oslc.pageSize=100&oslc.select=*&oslc.where=domainid%3D%22M-%25%22`, {
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ }
+ catch (err) {
+ postMessage(['error', 'Failed to fetch Data from Maximo, Please Check Network', err]);
+ return false;
+ }
+ const content = yield response.json();
+ if (content['responseInfo']['nextPage']) {
+ pageno = pageno + 1;
+ }
+ else {
+ nextpage = false;
+ }
+ content['member'].forEach((meter) => {
+ meters.push({
+ list_id: meter['domainid'],
+ inspect: meter['description'].slice(0, meter['description'].length - 9),
+ metername: meter['metername'],
+ });
+ });
+ }
+ return meters;
+ });
+ }
+ getObservations() {
+ return __awaiter(this, void 0, void 0, function* () {
+ // return meters and observations
+ let response;
+ let nextpage = true;
+ let pageno = 1;
+ const meters = [];
+ const observations = [];
+ while (nextpage) {
+ try {
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/iko_alndomain?lean=1&pageno=${pageno}&oslc.where=domainid%3D%22M-%25%22&oslc.pageSize=100&oslc.select=alndomain%2Cdomainid%2Cdescription`, {
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ }
+ catch (err) {
+ postMessage(['error', 'Failed to fetch Data from Maximo, Please Check Network', err]);
+ return false;
+ }
+ const content = yield response.json();
+ if (content['responseInfo']['nextPage']) {
+ pageno = pageno + 1;
+ }
+ else {
+ nextpage = false;
+ }
+ content['member'].forEach((meter) => {
+ meters.push({
+ list_id: meter['domainid'],
+ inspect: meter['description'],
+ search_str: `${meter['domainid']}~${meter['description']}`,
+ });
+ if (meter['alndomain']) {
+ meter['alndomain'].forEach((observation) => {
+ observations.push({
+ meter: meter['domainid'].slice(2),
+ id_value: observation['value'],
+ observation: observation['description'],
+ search_str: `${meter['domainid'].slice(2)}~${observation['value']}`,
+ });
+ });
+ }
+ else {
+ postMessage(['warning', `Meter: ${meter['domainid']} has no observation codes`]);
+ }
+ });
+ }
+ postMessage(['result', [meters, observations]]);
+ });
+ }
+ /**
+ * get updated inventory records from Maximo
+ * @param {int} rowstamp timestamp of latest cached inventory item
+ */
+ getNewInventory(rowstamp) {
+ return __awaiter(this, void 0, void 0, function* () {
+ let response;
+ try {
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/iko_inventory?lean=1&oslc.select=vendor,vendor.name,manufacturer,siteid,modelnum,itemnum,catalogcode,location&fetchmodedelta=1&lastfetchts=${rowstamp}`, {
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ }
+ catch (err) {
+ postMessage(['warning', 'Failed to fetch Data from Maximo, Please Check Network (1)', err]);
+ return false;
+ }
+ const content = yield response.json();
+ const inventory = [];
+ if (content['Error']) { // content["Error"]["message"]
+ postMessage(['warning', content['Error']]);
+ postMessage(['warning', 'Failed to fetch Data from Maximo, Please Check Network (2)']);
+ yield new Promise((resolve) => setTimeout(resolve, 5000));
+ }
+ else {
+ const newRowStamp = response.headers.get('maxrowstamp');
+ content['member'].forEach((item) => {
+ var _a, _b, _c, _d, _e, _f;
+ inventory.push([
+ item['itemnum'],
+ item['siteid'],
+ (_a = item['catalogcode']) !== null && _a !== void 0 ? _a : '',
+ (_b = item['modelnum']) !== null && _b !== void 0 ? _b : '',
+ (_c = item['$alias_this_attr$vendor']) !== null && _c !== void 0 ? _c : '',
+ (_d = item['manufacturer']) !== null && _d !== void 0 ? _d : '',
+ (_e = item['vendor']['name']) !== null && _e !== void 0 ? _e : '',
+ (_f = item['location']) !== null && _f !== void 0 ? _f : '',
+ newRowStamp,
+ ]);
+ });
+ return [inventory, newRowStamp];
+ }
+ });
+ }
+ /**
+ * get updated item records from Maximo
+ * @param {int} date timestamp of latest cached item
+ */
+ getNewItems(date) {
+ return __awaiter(this, void 0, void 0, function* () {
+ date = date.replace(' ', 'T');
+ let response;
+ try {
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/mxitem?lean=1&oslc.where=in22>"${date}" and itemnum="9%25"&oslc.select=itemnum,in22,description,issueunit,commoditygroup,externalrefid,status,description_longdescription`, {
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ }
+ catch (err) {
+ postMessage(['warning', 'Failed to fetch Data from Maximo, Please Check Network (1)', err]);
+ return false;
+ }
+ const content = yield response.json();
+ const items = [];
+ let previousDate = [new Date('2000-01-01'), ''];
+ let newDate = '';
+ if (content['Error']) { // content["Error"]["message"]
+ postMessage(['warning', content['Error']]);
+ postMessage(['warning', 'Failed to fetch Data from Maximo, Please Check Network (2)']);
+ yield new Promise((resolve) => setTimeout(resolve, 5000));
+ }
+ else {
+ content['member'].forEach((item) => {
+ newDate = item['in22'].replace('T', ' ').slice(0, -6);
+ items.push([
+ item['itemnum'],
+ item['description'],
+ newDate,
+ item['externalrefid'],
+ item['issueunit'],
+ item['commoditygroup'],
+ item['description_longdescription'],
+ ]);
+ if (previousDate[0] < new Date(newDate)) {
+ previousDate = [new Date(newDate), newDate];
+ }
+ });
+ return [items, previousDate[1]];
+ }
+ });
+ }
+ getNewManufacturers(date) {
+ var _a;
+ return __awaiter(this, void 0, void 0, function* () {
+ date = date.replace(' ', 'T');
+ let response;
+ try {
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/IKO_COMPMASTER?lean=1&oslc.where=type="M" and changedate>"${date}"&oslc.select=company,name,homepage,changedate`, {
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ }
+ catch (err) {
+ postMessage(['warning', 'Failed to fetch Data from Maximo, Please Check Network (1)', err]);
+ return false;
+ }
+ const content = yield response.json();
+ const items = [];
+ let previousDate = [new Date('2000-01-01'), ''];
+ let newDate = '';
+ if (content['Error']) { // content["Error"]["message"]
+ postMessage(['warning', (_a = content['Error']['message']) !== null && _a !== void 0 ? _a : content['Error']]);
+ postMessage(['warning', 'Failed to fetch Data from Maximo, Please Check Network (2)']);
+ yield new Promise((resolve) => setTimeout(resolve, 5000));
+ }
+ else {
+ content['member'].forEach((item) => {
+ newDate = item['changedate'].replace('T', ' ').slice(0, -6);
+ items.push([
+ item['company'],
+ newDate,
+ item['name'],
+ item['homepage'],
+ ]);
+ if (previousDate[0] < new Date(newDate)) {
+ previousDate = [new Date(newDate), newDate];
+ }
+ });
+ return [items, previousDate[1]];
+ }
+ });
+ }
+ /**
+ *
+ * @param {string} numSeries item series (99, 98, 91)
+ * @return {number} latest item number
+ */
+ getCurItemNumber(numSeries) {
+ return __awaiter(this, void 0, void 0, function* () {
+ let response;
+ try {
+ // %25 is %
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/mxitem?lean=1&oslc.where=status="active" and itemnum="${numSeries}%25"&_lid=${this.login.userid}&_lpwd=${this.login.password}&oslc.select=itemnum&oslc.pageSize=1&oslc.orderBy=-itemnum`, {
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ }
+ catch (err) {
+ postMessage(['debug', 'Failed to fetch data from Maximo, please check network (1)']); // this likely doesnt work, probably remove it
+ throw new Error('Failed to fetch data from Maximo, please check network (1)');
+ }
+ const content = yield response.json();
+ if (content['Error']) { // content["Error"]["message"]
+ postMessage(['debug', 'Failed to fetch Data from Maximo, Please Check Network (2)']); // this likely doesnt work, probably remove it
+ throw new Error('Failed to fetch data from Maximo, please check network (2)');
+ }
+ else {
+ try {
+ let number = content['member'][0]['itemnum'];
+ number = parseInt(number);
+ return number;
+ }
+ catch (_a) {
+ throw new Error('Invalid number series');
+ }
+ }
+ });
+ }
+ checkLogin(userid = this.login.userid, password = this.login.password) {
+ return __awaiter(this, void 0, void 0, function* () {
+ let response;
+ try {
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/whoami?lean=1`, {
+ headers: {
+ 'apikey': userid,
+ },
+ });
+ }
+ catch (err) {
+ postMessage(['result', 1, 'Failed to fetch Data from Maximo, Please Check Network (1)']);
+ return false;
+ }
+ const content = yield response.json();
+ if (content['Error']) {
+ postMessage(['result', 1, 'Failed to login to Maximo, Please Check User Name & Password']);
+ postMessage(['result', 1, content['Error']['message']]);
+ return false;
+ }
+ else {
+ const userSite = content['insertSite'];
+ const siteID = userSite;
+ const status = true;
+ this.shareDB.savePassword(userid, password);
+ this.login.password = password;
+ this.login.userid = userid;
+ postMessage(['debug', `Successfully logged in to Maximo as: ${content.displayName}`]);
+ postMessage(['result', 0, 'Successfully logged in to Maximo']);
+ return { siteID, status };
+ }
+ });
+ }
+ uploadToMaximo(item) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const xmldoc = `
+
+
+
+ ${item.commoditygroup}
+ ${item.description.replaceAll('&', '&')}
+ ${item.longdescription.replaceAll('&', '&')}
+ ${item.glclass}
+ ${item.assetprefix}
+ ${item.assetseed}
+ ${item.jpnum}
+ ${item.inspectionrequired}
+ ${item.isimport}
+ ${item.issueunit}
+ ${item.itemnumber}
+ ITEMSET1
+ ${item.rotating}
+ ACTIVE
+
+
+ `;
+ const response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/IKO_ITEMMASTER?action=importfile`, {
+ method: 'POST',
+ headers: {
+ 'filetype': 'XML',
+ 'apikey': this.login.userid,
+ // "preview": "1"
+ },
+ body: xmldoc,
+ });
+ const content = yield response.json();
+ const statuscode = response.status;
+ if (statuscode == 200) {
+ return parseInt(content.validdoc);
+ }
+ else {
+ throw new Error(parseInt(statuscode));
+ }
+ });
+ }
+ // Uploads item to inventory
+ uploadToInventory(item) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const xmldoc = `
+
+
+
+ ${item.cataloguenum}
+ ${item.issueunit}
+ ${item.itemnumber}
+ ITEMSET1
+ ${item.storeroomname}
+ ${item.siteID}
+ ${item.vendorname}
+
+
+ `;
+ const response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/IKO_INVENTORY?action=importfile`, {
+ method: 'POST',
+ headers: {
+ 'filetype': 'XML',
+ 'apikey': this.login.userid,
+ // "preview": "1"
+ },
+ body: xmldoc,
+ });
+ const content = yield response.json();
+ // if upload to storeroom succeeded
+ if (parseInt(content.validdoc) == 1) {
+ return 1;
+ } // failure due to invalid vendor name
+ else if (content['oslc:Error']['oslc:message'].includes('Company is not valid')) {
+ console.log(content['oslc:Error']['oslc:message']);
+ return 2;
+ } // failure due to invalid site id
+ else if (content['oslc:Error']['oslc:message'].includes('is not a valid site')) {
+ console.log(content['oslc:Error']['oslc:message']);
+ return 3;
+ } // failure due to invalid storeroom
+ else if (content['oslc:Error']['oslc:message'].includes('is not a valid inventory location')) {
+ console.log(content['oslc:Error']['oslc:message']);
+ return 4;
+ } // failure due to other reason i.e. item already has inventory fields filled in on Maximo
+ else {
+ console.log(content['oslc:Error']['oslc:message']);
+ return 0;
+ }
+ });
+ }
+ /**
+ * Uploads an image to maximo
+ *
+ * @param {File} image
+ * @returns {string[]} [status, (message if upload is unsuccessful)]
+ */
+ uploadImageToMaximo(image) {
+ return __awaiter(this, void 0, void 0, function* () {
+ // check valid image type
+ if (image.type !== 'image/jpeg' && image.type !== 'image/png') {
+ return ['fail', 'Image type not jpeg or png'];
+ }
+ // check if item number exists in maximo
+ const itemnum = image.name.slice(0, 7); // itemnum is first 7 digits of image name
+ let response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/mxitem?oslc.where=itemnum=${itemnum}`, {
+ method: 'GET',
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ let content = yield response.json();
+ if (content['rdfs:member'] == 0 || content['oslc:Error']) {
+ return ['fail', 'Item number not found'];
+ }
+ // get item id - item id is a code that lets you access information about the item through the API
+ let itemId = content['rdfs:member'][0]['rdf:resource'];
+ itemId = itemId.slice(38);
+ // console.log("item id " + itemId);
+ // check for existing image
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/mxitem/${itemId}`, {
+ method: 'GET',
+ headers: {
+ 'apikey': this.login.userid,
+ },
+ });
+ content = yield response.json();
+ // if image exists
+ if (content['_imagelibref']) {
+ // console.log("image exists");
+ // code to delete existing image
+ /* response = await fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/mxitem/${itemId}?action=system:deleteimage`, {
+ method: "POST",
+ headers: {
+ "x-method-override":"PATCH",
+ "apikey": this.login.userid,
+ }
+ });*/
+ // dont upload image
+ return ['warning', 'Image already exists for item number'];
+ }
+ // upload new image
+ response = yield fetch(`https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/api/os/mxitem/${itemId}?action=system:addimage`, {
+ method: 'POST',
+ headers: {
+ 'x-method-override': 'PATCH',
+ 'Slug': `${itemnum}.jpg`,
+ 'Content-type': 'image/jpeg',
+ 'custom-encoding': 'base',
+ 'apikey': this.login.userid,
+ },
+ body: image,
+ });
+ // console.log(response['statusText']);
+ return ['success'];
+ });
+ }
+}
+module.exports = Maximo;
diff --git a/built/misc/sharedDB.js b/built/misc/sharedDB.js
new file mode 100644
index 0000000..3b0b105
--- /dev/null
+++ b/built/misc/sharedDB.js
@@ -0,0 +1,55 @@
+"use strict";
+const Sql = require('better-sqlite3');
+/**
+ * Database class for setting related queries
+ */
+class SharedDatabase {
+ constructor() {
+ this.db = new Sql(`${process.env.APPDATA}/EAM Spare Parts/program.db`); // , { verbose: console.log });
+ const stmt = this.db.prepare('CREATE TABLE IF NOT EXISTS settings(id INTEGER PRIMARY KEY, key TEXT UNIQUE NOT NULL, value TEXT NOT NULL)');
+ stmt.run();
+ }
+ /**
+ * check if version db was last opened against matches curVersion, updates version in DB
+ * @param {String} curVersion running app version
+ * @return {boolean} if version is same as db version
+ */
+ checkVersion(curVersion) {
+ let stmt;
+ let lastVersion = '0.0.0';
+ try {
+ stmt = this.db.prepare(`SELECT value FROM settings WHERE key = 'version'`);
+ lastVersion = stmt.get().value;
+ stmt = this.db.prepare(`UPDATE settings SET value = '${curVersion}' WHERE key = 'version'`);
+ stmt.run();
+ }
+ catch (SqliteError) {
+ stmt = this.db.prepare('CREATE TABLE IF NOT EXISTS settings(id INTEGER PRIMARY KEY, key TEXT UNIQUE NOT NULL, value TEXT NOT NULL)');
+ stmt.run();
+ stmt = this.db.prepare(`INSERT INTO settings(key, value) VALUES ('version', '${curVersion}')`);
+ stmt.run();
+ }
+ return (lastVersion == curVersion);
+ }
+ savePassword(userid, password) {
+ let stmt;
+ stmt = this.db.prepare('CREATE UNIQUE INDEX if not EXISTS idx_key ON settings(key);'); // patch table to have unique keys
+ stmt.run();
+ stmt = this.db.prepare(`replace into settings(key, value) VALUES ('userid', '${userid}'), ('password', '${password}')`);
+ stmt.run();
+ }
+ getPassword() {
+ var _a, _b;
+ try {
+ let stmt = this.db.prepare(`SELECT value FROM settings WHERE key = 'userid'`);
+ const userid = (_a = stmt.get()) === null || _a === void 0 ? void 0 : _a.value;
+ stmt = this.db.prepare(`SELECT value FROM settings WHERE key = 'password'`);
+ const password = (_b = stmt.get()) === null || _b === void 0 ? void 0 : _b.value;
+ return { userid: userid, password: password };
+ }
+ catch (SqliteError) {
+ return { userid: '', password: '' };
+ }
+ }
+}
+module.exports = SharedDatabase;
diff --git a/built/misc/spreadsheet.js b/built/misc/spreadsheet.js
new file mode 100644
index 0000000..0a6427f
--- /dev/null
+++ b/built/misc/spreadsheet.js
@@ -0,0 +1,284 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+const Exceljs = require('exceljs');
+const fs = require('fs');
+const dt = require('luxon');
+/**
+ * class for reading data from excel item cache
+ */
+class ExcelReader {
+ constructor(filePath) {
+ this.filePath = filePath;
+ }
+ // the version number of the workbook is saved in a cell for tracking purposes
+ getVersion() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const wb = new Exceljs.Workbook();
+ yield wb.xlsx.readFile(this.filePath);
+ const ws = wb.getWorksheet('Sheet2');
+ const version = dt.DateTime.fromSeconds((parseFloat(ws.getCell('A2').text) - 25569) * 86400 + 14400).toFormat('yyyy-LL-dd HH:mm:ss');
+ return version;
+ });
+ }
+ // read information about the item database (an initial file is included for
+ // faster startup rather than fetching all 100k+ items from maximo directly)
+ /**
+ * read the item cache file
+ */
+ getItemCache() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const wb = new Exceljs.Workbook();
+ yield wb.xlsx.readFile(this.filePath);
+ // read inventory data which will be appended to ext_search_text
+ const ws3 = wb.getWorksheet('Sheet3');
+ let lastRow = ws3.lastRow.number;
+ const inventoryData = new Map();
+ const allInventory = [];
+ for (let i = 2; i <= lastRow; i++) {
+ const row = [];
+ row[0] = ws3.getCell(`A${i}`).text;
+ row[1] = ws3.getCell(`B${i}`).text;
+ row[2] = ws3.getCell(`C${i}`).text;
+ row[3] = ws3.getCell(`D${i}`).text;
+ row[4] = ws3.getCell(`E${i}`).text;
+ row[5] = ws3.getCell(`F${i}`).text;
+ row[6] = ws3.getCell(`G${i}`).text;
+ row[7] = ws3.getCell(`I${i}`).text;
+ allInventory.push([...row, ws3.getCell(`H${i}`).text]);
+ if (row[2].length > 0 || row[3].length > 0 || row[4].length > 0 || row[5].length > 0 || row[6].length > 0) {
+ if (inventoryData.has(row[0])) {
+ for (let j = 1; j <= 7; j++) {
+ if (row[j].length > 0 && row[j] != 'NULL') {
+ inventoryData.get(row[0])[j] = inventoryData.get(row[0])[j] + '|' + row[j];
+ }
+ }
+ }
+ else {
+ for (let j = 2; j <= 7; j++) {
+ if (row[j] == 'NULL') {
+ row[j] = '';
+ }
+ }
+ inventoryData.set(row[0], row);
+ }
+ }
+ }
+ // read item master data
+ const ws = wb.getWorksheet('Sheet1'); // alternatively (fetch by ID): getWorksheet(1);
+ lastRow = ws.lastRow.number; // last cell row in range
+ const data = []; // empty list
+ for (let i = 2; i <= lastRow; i++) {
+ try {
+ data.push([
+ ws.getCell(`A${i}`).text,
+ ws.getCell(`B${i}`).text,
+ dt.DateTime.fromSeconds((parseFloat(ws.getCell(`C${i}`).text) - 25569) * 86400 + 14400).toFormat('yyyy-LL-dd HH:mm:ss'),
+ ws.getCell(`D${i}`).text,
+ ws.getCell(`E${i}`).text,
+ ws.getCell(`F${i}`).text,
+ ws.getCell(`H${i}`).text,
+ inventoryData.get(ws.getCell(`A${i}`).text),
+ ]);
+ }
+ catch (error) {
+ console.log(error);
+ console.log(`row number: ${i}`);
+ }
+ }
+ const ws2 = wb.getWorksheet('Sheet2');
+ return [
+ data,
+ dt.DateTime.fromSeconds((parseFloat(ws2.getCell('A2').text) - 25569) * 86400 + 14400).toFormat('yyyy-LL-dd HH:mm:ss'),
+ allInventory,
+ ];
+ // to convert excel datetime in number format to string
+ });
+ }
+ // get inital list of manufacturers from the workbook
+ getManufactures() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const workbook = new Exceljs.Workbook();
+ yield workbook.xlsx.readFile(this.filePath);
+ const worksheet = workbook.getWorksheet('Manufacturers');
+ const lastrow = worksheet.lastRow.number;
+ const data = [];
+ for (let i = 2; i <= lastrow; i++) {
+ if (worksheet.getCell(`A${i}`).text) {
+ data.push([
+ worksheet.getCell(`A${i}`).text,
+ dt.DateTime.fromSeconds((parseFloat(worksheet.getCell(`B${i}`).text) - 25569) * 86400 + 14400).toFormat('yyyy-LL-dd HH:mm:ss'),
+ worksheet.getCell(`C${i}`).text,
+ worksheet.getCell(`D${i}`).text,
+ ]);
+ }
+ }
+ return data;
+ });
+ }
+ // get initial list of abbreviations from the workbook
+ getAbbreviations() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const workbook = new Exceljs.Workbook();
+ yield workbook.xlsx.readFile(this.filePath);
+ const worksheet = workbook.getWorksheet('Replacements');
+ const lastrow = worksheet.lastRow.number;
+ const data = [];
+ for (let i = 3; i <= lastrow; i++) {
+ if (worksheet.getCell(`D${i}`).text) {
+ data.push([worksheet.getCell(`D${i}`).text, worksheet.getCell(`B${i}`).text]);
+ }
+ }
+ return data;
+ });
+ }
+ // read item information from workbook being processed
+ getDescriptions(params) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const workbook = new Exceljs.Workbook();
+ yield workbook.xlsx.readFile(this.filePath);
+ fs.copyFileSync(this.filePath, `${this.filePath}.backup`);
+ postMessage(['info', `Backing up file as: "${this.filePath}.backup"`]);
+ const wsNames = workbook.worksheets.map(function (ele) {
+ return ele.name;
+ });
+ if (!wsNames.includes(params.wsName)) {
+ postMessage(['info', 'Workbook has the following worksheets:']);
+ postMessage(['info', `${wsNames}`]);
+ postMessage([
+ 'error',
+ `"${params.wsName} does not exist, Please check spelling & captitalization"`,
+ ]);
+ return false;
+ }
+ const worksheet = workbook.getWorksheet(params.wsName);
+ const lastrow = worksheet.lastRow.number;
+ const data = [];
+ let row = [];
+ for (let i = params.startRow; i <= lastrow; i++) {
+ row = [];
+ for (let j = 0; j < params.inDesc.length; j++) {
+ if (worksheet.getCell(`${params.inDesc[j]}${i}`).text) {
+ row.push(worksheet.getCell(`${params.inDesc[j]}${i}`).text);
+ }
+ }
+ data.push([i, row.join()]);
+ }
+ return data;
+ });
+ }
+ // write validated item information to the workbook
+ // not used
+ writeDescriptions(descriptions, savePath) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const workbook = xlsx.readFile(this.filePath, { cellStyles: true, bookVBA: true });
+ const worksheet = workbook.Sheets['Validate'];
+ descriptions.forEach((description) => {
+ worksheet[`E${description.row}`] = { t: `s`, v: description.result[3], w: undefined }; // maximo description
+ worksheet[`F${description.row}`] = { t: `s`, v: description.result[0], w: undefined }; // main description
+ worksheet[`G${description.row}`] = { t: `s`, v: description.result[1], w: undefined }; // ext1
+ worksheet[`H${description.row}`] = { t: `s`, v: description.result[2], w: undefined }; // ext2
+ worksheet[`I${description.row}`] = { t: `s`, v: description.messages, w: undefined }; // msg
+ });
+ try {
+ xlsx.writeFile(workbook, savePath);
+ }
+ catch (error) {
+ console.log(error);
+ const suffix = Date.now();
+ savePath = savePath.split(`.`);
+ savePath = `${savePath[0]}${suffix}.${savePath[1]}`;
+ xlsx.writeFile(workbook, savePath);
+ }
+ return savePath;
+ });
+ }
+ saveDescription(parmas) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const workbook = new Exceljs.Workbook();
+ yield workbook.xlsx.readFile(this.filePath);
+ const worksheet = workbook.getWorksheet(parmas[0].wsName);
+ worksheet.getCell(`${parmas[0].outItemDesc[0]}${parmas[0].outRow}`).value = parmas[1][0]; // description1
+ worksheet.getCell(`${parmas[0].outItemDesc[1]}${parmas[0].outRow}`).value = parmas[1][1]; // description2
+ worksheet.getCell(`${parmas[0].outItemDesc[2]}${parmas[0].outRow}`).value = parmas[1][2]; // manufacturer
+ worksheet.getCell(`${parmas[0].outUOM}${parmas[0].outRow}`).value = parmas[0].uom; // uom
+ worksheet.getCell(`${parmas[0].outComm}${parmas[0].outRow}`).value = parmas[0].commGroup; // c-group
+ // worksheet.getCell(`${parmas[0].outItemDesc[2]}${parmas[0].outRow}`).value = parmas[1][2]; //c-code
+ worksheet.getCell(`${parmas[0].outGL}${parmas[0].outRow}`).value = parmas[0].glClass; // gl-class
+ worksheet.getCell(`${parmas[0].outTranslate}${parmas[0].outRow}`).value =
+ 'placeholder-translated'; // translated
+ worksheet.getCell(`${parmas[0].outMissing}${parmas[0].outRow}`).value =
+ 'placeholder-missing'; // missing
+ // worksheet.getCell(`${parmas[0].outItemDesc[2]}${parmas[0].outRow}`).value = parmas[1][2]; //question
+ yield this.saveWorkbook(workbook, this.filePath);
+ });
+ }
+ saveNumber(parmas) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const workbook = new Exceljs.Workbook();
+ yield workbook.xlsx.readFile(this.filePath);
+ const worksheet = workbook.getWorksheet(parmas[1]);
+ worksheet.getCell(`${parmas[3]}${parmas[2]}`).value = parmas[4];
+ return yield this.saveWorkbook(workbook, this.filePath);
+ });
+ }
+ saveNonInteractive(parmas, data) {
+ return __awaiter(this, void 0, void 0, function* () {
+ // convert to batch mode, individually it is too slow
+ const workbook = new Exceljs.Workbook();
+ yield workbook.xlsx.readFile(this.filePath);
+ const worksheet = workbook.getWorksheet(parmas[0].wsName);
+ for (const item of data) {
+ item.analysis = JSON.parse(item.analysis);
+ if (item.analysis.related) {
+ worksheet.getCell(`${parmas[0].outItemNum}${item.row}`).value =
+ item.analysis.related; // itemnum
+ worksheet.getCell(`${parmas[0].outItemDesc[0]}${item.row}`).value =
+ item.description;
+ }
+ if (item.analysis.translate) {
+ worksheet.getCell(`${parmas[0].outTranslate}${item.row}`).value =
+ item.analysis.translate.description; // translated description
+ worksheet.getCell(`${parmas[0].outMissing}${item.row}`).value =
+ item.analysis.translate.missing.join('|'); // missing translations windows wants \r\n instead of just \n
+ }
+ // worksheet.getCell(`${parmas[0].outItemDesc}${item.row}`).value = parmas[2]; // en description
+ }
+ return yield this.saveWorkbook(workbook, this.filePath);
+ });
+ }
+ saveWorkbook(workbook, savePath) {
+ return __awaiter(this, void 0, void 0, function* () {
+ try {
+ yield workbook.xlsx.writeFile(savePath);
+ }
+ catch (error) {
+ console.log(error);
+ postMessage([`error`, `Cannot edit old file make sure it is closed`]);
+ }
+ return savePath;
+ });
+ }
+ getColumnByName(name) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const wb = new Exceljs.Workbook();
+ yield wb.xlsx.readFile(this.filePath);
+ const ws = wb.getWorksheet('Sheet1');
+ let match;
+ ws.eachRow((row) => row.eachCell((cell) => {
+ if (cell.names.find((n) => n === name)) {
+ match = cell;
+ }
+ }));
+ return match;
+ });
+ }
+}
+module.exports = ExcelReader;
diff --git a/built/misc/utils.js b/built/misc/utils.js
new file mode 100644
index 0000000..2b88c5a
--- /dev/null
+++ b/built/misc/utils.js
@@ -0,0 +1,36 @@
+"use strict";
+function getCombinations(valuesArray) {
+ let combi = [];
+ let temp = [];
+ let slent = Math.pow(2, valuesArray.length);
+ for (let i = 0; i < slent; i++) {
+ temp = [];
+ for (let j = 0; j < valuesArray.length; j++) {
+ if ((i & Math.pow(2, j))) {
+ temp.push(valuesArray[j]);
+ }
+ }
+ if (temp.length > 0) {
+ combi.push(temp);
+ }
+ }
+ combi.sort((a, b) => a.length - b.length);
+ return combi;
+}
+function inOrderCombinations(valuesArray) {
+ let combi = [];
+ for (let i = 0; i < valuesArray.length; i++) {
+ for (let j = 0; j < valuesArray.length - i; j++) {
+ combi.push(valuesArray.slice(j, j + i + 1));
+ }
+ }
+ return combi;
+}
+function splitToTwo() {
+ // [string] for english
+}
+function splitToThree() {
+ //{description: [string], manu: string}
+}
+const STRINGCLEANUP = [',', ' ', '-'];
+module.exports = { getCombinations, inOrderCombinations, STRINGCLEANUP };
diff --git a/built/misc/validators.js b/built/misc/validators.js
new file mode 100644
index 0000000..f4c9a84
--- /dev/null
+++ b/built/misc/validators.js
@@ -0,0 +1,144 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+const Database = require('./indexDB');
+const ExcelReader = require('./spreadsheet');
+const utils = require('./utils');
+class ManufacturerValidator {
+ constructor() {
+ this.CHAR_LIMIT = 30;
+ this.db = new Database();
+ }
+ validateSingle(split_desc) {
+ let manufacturer = false;
+ // look from end of string since manufacturer name is mostly likely in last description
+ for (let i = split_desc.length - 1; i >= 0; i--) {
+ postMessage(['debug', `Looking for manufacturer named: "${split_desc[i]}"`]);
+ manufacturer = this.db.isManufacturer(split_desc[i]);
+ if (manufacturer) {
+ postMessage(['debug', `Found manufacturer with short name: "${manufacturer.short_name}"`]);
+ split_desc.splice(i, 1); //remove the manufactuer and re-add the short version to the end
+ split_desc.push(manufacturer.short_name);
+ return split_desc;
+ }
+ }
+ }
+}
+class PhraseReplacer {
+ constructor() {
+ this.db = new Database();
+ }
+ replaceAbbreviated(split_desc) {
+ let replacement = false;
+ for (let i = 0; i < split_desc.length; i++) {
+ // look for replacements for phrases
+ replacement = this.db.isAbbreviation(split_desc[i].replace('-', ' '));
+ if (replacement) {
+ postMessage(['debug', `Replacing: "${split_desc[i]} with: ${replacement.replace_text}"`]);
+ split_desc[i] = replacement.replace_text;
+ }
+ // look for replacement for individual words
+ let word_split = utils.inOrderCombinations(split_desc[i].split(' '));
+ replacement = false;
+ for (let j = word_split.length - 1; j > 0; j--) {
+ replacement = this.db.isAbbreviation(word_split[j].join(' ').replace('-', ' '));
+ if (replacement) {
+ postMessage(['debug', `Replacing: "${word_split[j].join(' ')} with: ${replacement.replace_text}"`]);
+ split_desc[i] = split_desc[i].replace(word_split[j].join(' '), replacement.replace_text);
+ }
+ }
+ }
+ return split_desc;
+ }
+}
+class Validate {
+ validateSingle(raw_desc) {
+ return __awaiter(this, void 0, void 0, function* () {
+ raw_desc = raw_desc.split(',');
+ let split_desc = [];
+ raw_desc.forEach(desc => {
+ split_desc.push(desc.trim());
+ });
+ postMessage(['debug', `Validating: "${split_desc}"`]);
+ let manuValid = new ManufacturerValidator();
+ let manu = yield manuValid.validateSingle(split_desc);
+ if (!manu) {
+ postMessage(['debug', `No manufacturer found in: "${raw_desc}"`]);
+ }
+ else {
+ split_desc = manu;
+ }
+ let replace = new PhraseReplacer();
+ let replaced = yield replace.replaceAbbreviated(split_desc);
+ if (replaced) {
+ split_desc = replaced;
+ }
+ else {
+ postMessage(['debug', `No words need to be replaced`]);
+ }
+ let result = this.assembleDescription(split_desc);
+ return result;
+ });
+ }
+ validateBatch(filePath) {
+ return __awaiter(this, void 0, void 0, function* () {
+ postMessage(['debug', `Selected file path: "${filePath}"`]);
+ filePath = filePath[0];
+ let excel = new ExcelReader(filePath);
+ let result = yield excel.getDescriptions();
+ for (let i = 0; i < result.length; i++) {
+ result[i].result = yield this.validateSingle(result[i].value);
+ }
+ filePath = filePath.split('.');
+ filePath = `${filePath[0]}_Validated.${filePath[1]}`;
+ filePath = yield excel.writeDescriptions(result, filePath);
+ return (filePath);
+ });
+ }
+ assembleDescription(split_desc) {
+ const db = new Database();
+ let descriptions = ['', '', '', '']; // jde1, jde2, jde3, maximo descriptions
+ let position = 0; //tracks which part of jde description is being added to
+ const regex = /\d+/g;
+ for (let i = 0; i < split_desc.length; i++) {
+ if (!(split_desc[i].match(regex))) { // if the phrase has no numbers
+ split_desc[i] = split_desc[i].toUpperCase();
+ }
+ // if we are at end of array & the phrase is a manufacturer
+ if (i + 1 == split_desc.length && db.isManufacturer(split_desc[i])) {
+ if (descriptions[2].length == 0) {
+ descriptions[2] = split_desc[i];
+ }
+ else {
+ descriptions[2] = `${descriptions[2]},${split_desc[i]}`;
+ }
+ }
+ // two ifs to avoid trailing / leading commas
+ else if (descriptions[position].length == 0) {
+ descriptions[position] = split_desc[i];
+ }
+ else if (`${descriptions[position]},${split_desc[i]}`.length <= 30) {
+ descriptions[position] = `${descriptions[position]},${split_desc[i]}`;
+ }
+ else { // if too long put it into next word
+ position++;
+ if (position == 3) { // if jde is over flowing into Maximo
+ postMessage(['warning', 'Description is too long']);
+ }
+ else {
+ descriptions[position] = split_desc[i];
+ }
+ }
+ }
+ descriptions[3] = descriptions.slice(0, 3).filter(Boolean).join(',');
+ return descriptions;
+ }
+}
+module.exports = Validate;
diff --git a/built/renderer/asset_translation.js b/built/renderer/asset_translation.js
new file mode 100644
index 0000000..644c597
--- /dev/null
+++ b/built/renderer/asset_translation.js
@@ -0,0 +1,67 @@
+"use strict";
+// Debug stuff
+document.getElementById("selected_file").innerHTML = 'C:\\Users\\majona\\Documents\\GitHub\\iko_mro_items\\assets\\Translation_PM_JP_Asset.xlsx';
+document.getElementById("selected_jobtasks").innerHTML = `C:\\Users\\majona\\Documents\\jp_pm_translation-test_ant.xlsx`;
+const { ipcRenderer } = require('electron');
+document.getElementById("topButton").addEventListener("click", toTop);
+document.getElementById("endButton").addEventListener("click", toEnd);
+document.getElementById("select_file").addEventListener("click", selectFile);
+document.getElementById("select_jobtasks").addEventListener("click", selectJobTasks);
+document.getElementById("process").addEventListener("click", processFile);
+document.getElementById("dark-mode-switch").addEventListener("click", toggleTheme);
+function selectFile() {
+ ipcRenderer.invoke('select-translations', 'finished').then((result) => {
+ if (!result.canceled) {
+ document.getElementById("selected_file").innerHTML = result.filePaths[0];
+ }
+ else {
+ new Toast('File Picker Cancelled');
+ }
+ });
+}
+function selectJobTasks() {
+ ipcRenderer.invoke('select-to-be-translated', 'finished').then((result) => {
+ if (!result.canceled) {
+ document.getElementById("selected_jobtasks").innerHTML = result.filePaths[0];
+ }
+ else {
+ new Toast('File Picker Cancelled');
+ }
+ });
+}
+function processFile() {
+ let bar = new ProgressBar;
+ const site_id = document.getElementById('siteSelect').value;
+ if (site_id === '0') {
+ bar.update(100, 'Please select a site');
+ new Toast('Please select a site');
+ }
+ else {
+ const worker = new WorkerHandler;
+ const wb_translation = document.getElementById("selected_file").innerHTML;
+ const wb_pms = document.getElementById("selected_jobtasks").innerHTML;
+ bar.update(0, 'Processing Translations');
+ worker.work([
+ 'translatepms',
+ {
+ wb_translation: wb_translation,
+ wb_pms: wb_pms,
+ siteid: site_id
+ }
+ ], showResults);
+ }
+}
+function showResults(results) {
+ let bar = new ProgressBar;
+ bar.update(100, 'Done!');
+}
+function toggleTheme() {
+ theme = document.getElementById("dark-mode-switch").checked ? "light" : "dark";
+ let str = "[data-bs-theme=\"" + theme + "\"]";
+ theme = document.getElementById("dark-mode-switch").checked ? "dark" : "light";
+ //console.log(str);
+ let elms = document.querySelectorAll(str);
+ for (const elm of elms) {
+ elm.setAttribute("data-bs-theme", theme);
+ }
+}
diff --git a/built/renderer/item_main.js b/built/renderer/item_main.js
new file mode 100644
index 0000000..731e8e2
--- /dev/null
+++ b/built/renderer/item_main.js
@@ -0,0 +1,1360 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+const { clipboard, ipcRenderer, shell } = require('electron');
+const fs = require('fs');
+const path = require('path');
+// const { dialog } = require('electron').remote;
+const Database = require('../misc/indexDB');
+const SharedDatabase = require('../misc/sharedDB');
+const Validate = require('../misc/validators');
+const Maximo = require('../misc/maximo');
+const CONSTANTS = require('../misc/constants.js');
+// stores items that are to be uploaded through the "batch upload" accordion.
+let itemsToUpload = [];
+// stores images that are to be uploaded through the "image upload" accordion.
+let imgsToUpload = [];
+// an object that stores the location of each column in the batch upload table.
+// allows for column locations to be interchanged. -1 means a column is not in
+// the table. Maybe in the future, column locations should be predetermined so
+// that a global variable is not used for this.
+let colLoc = {
+ description: -1,
+ uom: -1,
+ commGroup: -1,
+ glClass: -1,
+ maximo: -1,
+ vendor: -1,
+ storeroom: -1,
+ catNum: -1,
+ siteID: -1,
+};
+// an object that stores all search results and "bookmarks" how many items have
+// been loaded in the related results table. Used for infinite scroll.
+let relatedResults = {
+ idx: 0, // store the index of the current item being loaded
+ curKey: 0, // store which key is currently being loaded from the search results
+ results: [], // store all search results (dictonary)
+};
+// a function that is called immediately after the window has been loaded
+window.onload = function () {
+ // set the darkmode toggle to the correct position by retreiving information from the local storage
+ document.getElementById('dark-mode-switch').checked = (localStorage.getItem('theme') === 'dark' ? true : false);
+ // change the UI based on whether the user is a "power user". show all upload elements if they are a power user, else hide it.
+ if (localStorage.getItem('powerUser') === 'true') {
+ document.getElementById('upload-btn').style.display = 'block';
+ document.getElementById('request-btn').style.display = 'none';
+ document.getElementById('batch-upld-btn').style.display = 'block';
+ document.getElementById('img-upld-toggle').style.display = 'block';
+ document.getElementById('batch-mode-toggle').style.display = 'block';
+ return;
+ }
+};
+/*
+Power User Toggle
+
+Allows user to toggle between 2 modes: Power user and Normal User.
+Normal user mode hides all upload elements and only allows the user to request items.
+Power user mode shows all upload elements and allows the user to upload items and images.
+
+Created for the purpose of hiding upload functionality from people who shouldn't be
+uploading items (a.k.a. everyone except for reliability team).
+*/
+// set the user to a power user if they have clicked the secret button 5 times
+document.getElementById('secret-button').addEventListener('click', (e) => {
+ let isPowerUser = false;
+ let numClicks = parseInt(e.target.getAttribute('data-clicks'));
+ numClicks++;
+ if (numClicks === 5) {
+ isPowerUser = true;
+ localStorage.setItem('powerUser', 'true');
+ e.target.setAttribute('data-clicks', '0');
+ }
+ else {
+ localStorage.setItem('powerUser', 'false');
+ e.target.setAttribute('data-clicks', `${numClicks}`);
+ isPowerUser = false;
+ }
+ // toggle whether elements are hidden or not based off of power user status
+ if (isPowerUser == true) {
+ document.getElementById('upload-btn').style.display = 'block';
+ document.getElementById('request-btn').style.display = 'none';
+ document.getElementById('batch-upld-btn').style.display = 'block';
+ document.getElementById('img-upld-toggle').style.display = 'block';
+ document.getElementById('batch-mode-toggle').style.display = 'block';
+ }
+ else {
+ document.getElementById('upload-btn').style.display = 'none';
+ document.getElementById('request-btn').style.display = 'block';
+ document.getElementById('batch-upld-btn').style.display = 'none';
+ document.getElementById('img-upld-toggle').style.display = 'none';
+ document.getElementById('batch-mode-toggle').style.display = 'none';
+ }
+});
+// gets user site information
+function getSite(credentials = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const maximo = new Maximo();
+ const currInfo = yield maximo.checkLogin(credentials === null || credentials === void 0 ? void 0 : credentials.userid, credentials === null || credentials === void 0 ? void 0 : credentials.password);
+ return currInfo.siteID;
+ });
+}
+// open a modal that allows you to make an item request
+document.getElementById('request-btn').addEventListener('click', () => {
+ // show request item modal
+ const requestModal = new bootstrap.Modal(document.getElementById('requestModal'));
+ requestModal.toggle();
+ const currPass = new SharedDatabase().getPassword();
+ const userid = currPass.userid;
+ let siteID;
+ const sites = {
+ 'AA': ['AAG: Brampton B2 Storeroom', 'AAL: Brampton B2/B4 Maintenance Storeroom', 'AAO: Brampton B4 Oxidizer Storeroom'],
+ 'ANT': ['AN1: Antwerp Mod Line Storeroom', 'AN2: Antwerp Coating Line Storeroom'],
+ 'BA': ['BAL: IKO Calgary Maintenance Storeroom'],
+ 'BL': ['BLC: Hagerstown TPO Storeroom', 'BLD: Hagerstown ISO Storeroom', 'BLL: Hagerstown Maintenance Storeroom(Shared)'],
+ 'CA': ['CAL">IKO Kankakee Maintenance Storeroom'],
+ 'CAM': ['C61">IKO Appley Bridge Maintenance Storeroom'],
+ 'COM': ['CB1">Combronde Maintenance Storeroom'],
+ 'GA': ['GAL: IKO Wilmington Maintenance Storeroom'],
+ 'GC': ['GCL: Sumas Maintenance Storeroom', 'GCA: Sumas Shipping Storeroom', 'GCD: Sumas Shingle Storeroom', 'GCG: Sumas Mod Line Storeroom', 'GCJ: Sumas Crusher Storeroom', 'GCK: Sumas Tank Farm Storeroom'],
+ 'GE': ['GEL: Ashcroft Maintenance Storeroom'],
+ 'GH': ['GHL: IKO Hawkesbury Maintenance Storeroom'],
+ 'GI': ['GIL: IKO Madoc Maintenance Storeroom'],
+ 'GJ': ['GJL: CRC Toronto Maintenance Storeroom'],
+ 'GK': ['GKA: IG Brampton B7 and B8 Storeroom', 'GKC: IG Brampton B6 and Laminator Storeroom', 'GKL: IG Brampton Maintenance Storeroom'],
+ 'GM': ['GML: IG High River Maintenance Storeroom'],
+ 'GP': ['GPL: CRC Brampton Maintenance Storeroom'],
+ 'GR': ['GRL: Bramcal Maintenance Storeroom'],
+ 'GS': ['GSL: Sylacauga Maintenance Storeroom'],
+ 'GV': ['GVL: IKO Hillsboro Maintenance Storeroom'],
+ 'GX': ['GXL: Maxi-Mix Maintenance Storeroom'],
+ 'KLU': ['KD1: IKO Klundert Maintenance Storeroom', 'KD2: IKO Klundert Lab Storeroom', 'KD3: IKO Klundert Logistics Storeroom'],
+ 'PBM': ['PB6: Slovakia Maintenance Storeroom'],
+ 'RAM': ['RA6: IKO Alconbury Maintenance Storeroom'],
+ // Add more sites and storerooms as needed...
+ };
+ const userSite = getSite({ userid: userid, password: currPass.password });
+ userSite.then((response) => {
+ siteID = response;
+ const storeroomSelect = document.getElementById('storeroom');
+ // poppulate correct user storerooms in modal
+ function updateStoreroomOptions() {
+ storeroomSelect.options.length = 1;
+ // Add new options
+ const neededStorerooms = sites[siteID];
+ for (const storeroom of neededStorerooms) {
+ const option = document.createElement('option');
+ option.value = storeroom;
+ option.text = storeroom;
+ storeroomSelect.add(option);
+ }
+ }
+ updateStoreroomOptions();
+ })
+ .catch((error) => console.error(`Error: ${error}`));
+ poppulateModal();
+});
+// Allow input of manufacturer name & part number if "Other" is selected
+document.getElementById('manu-name').addEventListener('click', (e) => {
+ if (e.target.value == 'Other') {
+ document.getElementById('pref-manu').style.display = 'block';
+ document.getElementById('part-form').style.display = 'block';
+ }
+ else {
+ document.getElementById('pref-manu').style.display = 'none';
+ document.getElementById('part-form').style.display = 'none';
+ }
+});
+// download email file when submit button is pressed
+document.getElementById('submit-btn').addEventListener('click', submitMail, false);
+// opens email file in default mail client
+function submitMail() {
+ // checking required fields are filled
+ if (document.getElementById('manu-name').value == 'Other') {
+ if (!(document.getElementById('part-num').reportValidity() &&
+ document.getElementById('storeroom').reportValidity() &&
+ document.getElementById('item-descr').reportValidity())) {
+ console.log('Required fields still empty');
+ return;
+ }
+ }
+ else {
+ if (!(document.getElementById('storeroom').reportValidity() &&
+ document.getElementById('item-descr').reportValidity())) {
+ console.log('Required fields still empty');
+ return;
+ }
+ }
+ // storing current date and time for email subject
+ const currentdate = new Date();
+ const datetime = currentdate.getFullYear() + '/' + (currentdate.getMonth() + 1) + '/' + (currentdate.getDay() + 1) +
+ ' @ ' +
+ currentdate.getHours() + ':' +
+ currentdate.getMinutes() + ':' + currentdate.getSeconds();
+ const mailText = ``;
+ // Send string to main process to write file
+ ipcRenderer.send('write-file', mailText);
+ // requestModal.toggle();
+}
+/* Infinite scroll
+
+Allows elements to load as the user scrolls down the page,
+drastically decreasing loading times and making UI smoother.
+*/
+// listen for a scroll event. if the bottom of the results table is less than 100px below the bottom of the viewport, load more items
+document.getElementById('everything').addEventListener('scroll', () => {
+ // dont add items to the list if the accordion is collapsed
+ if (document.getElementById('related-items-accordion-btn').classList.contains('collapsed') || relatedResults.results.length == 0) {
+ return;
+ }
+ const searchResultsTable = document.getElementById('related-items');
+ const domRect = searchResultsTable.getBoundingClientRect();
+ const spaceBelow = document.getElementById('everything').offsetHeight - domRect.bottom;
+ if (spaceBelow > -100) {
+ // load more items if the bottom of the table is less than 100px below the bottom of the viewport
+ loadRelated();
+ }
+});
+// Generate UI when files are selected by user
+document.getElementById('imgInput').addEventListener('change', (e) => __awaiter(void 0, void 0, void 0, function* () {
+ const progressBar = new ProgressBar();
+ // reset UI
+ document.getElementById('imgList').innerHTML = ``;
+ // get files from file picker
+ const files = document.getElementById('imgInput').files;
+ imgsToUpload = files;
+ // make a comma separated string of all the item numbers that are to be uploaded
+ let nums = '';
+ // if no files were selected, return
+ if (files.length == 0 || !files) {
+ return;
+ }
+ const imgList = document.getElementById('imgList');
+ progressBar.update(0, 'Loading Images...');
+ // for each image, add list item to HTML list
+ for (let i = 0; i < files.length; i++) {
+ const file = files[i];
+ const completion = (i + 1) / files.length * 100;
+ nums += file.name.slice(0, 7) + ','; // get first 7 characters of file name
+ progressBar.updateProgressBar(completion);
+ imgList.innerHTML += `
+
+
+
+
${file.name.slice(0, 7)}
+
+ pending
+
`;
+ }
+ // generate a link to open items that are being uploaded to in maximo
+ const url = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${nums}`;
+ document.getElementById('img-upload-status-text').innerHTML = `Selected Items:`;
+ document.getElementById('imgs-link').addEventListener('click', function (e) {
+ e.preventDefault();
+ shell.openExternal(url);
+ });
+ progressBar.update(100, 'Ready to Upload!');
+}));
+// clear the file picker each time it is clicked
+document.getElementById('imgInput').addEventListener('click', () => {
+ document.getElementById('img-clear-btn').dispatchEvent(new Event('click'));
+});
+document.getElementById('img-clear-btn').addEventListener('click', () => {
+ // reset all related components
+ const progressBar = new ProgressBar();
+ document.getElementById('imgList').innerHTML = ``;
+ progressBar.update(100, 'Ready!');
+ // empty list of images to upload
+ imgsToUpload = [];
+ // empty file picker
+ document.getElementById('imgInput').value = null;
+ document.getElementById('img-upload-status-text').innerHTML = 'Select Images to Continue...';
+});
+document.getElementById('img-upload-btn').addEventListener('click', () => {
+ const progressBar = new ProgressBar();
+ const clearBtn = document.getElementById('img-clear-btn');
+ const uploadBtn = document.getElementById('img-upload-btn');
+ // return if user has picked no images
+ if (imgsToUpload.length == 0) {
+ new Toast('No Images Selected!');
+ return;
+ }
+ let finishedItems = 0;
+ // disable the clear and upload buttons while upload is taking place so the
+ // user can't send duplicate requests or accidentally clear the image upload list
+ // while its uploading
+ clearBtn.disabled = true;
+ uploadBtn.disabled = true;
+ const worker = new WorkerHandler();
+ progressBar.update(0, 'Uploading Images...');
+ // upload all images and update UI
+ worker.work(['uploadImages', imgsToUpload], (result) => {
+ if (result[0] == 'success') {
+ // if success, display checkmark
+ document.getElementById(`img-${result[1]}-status`).innerHTML = `done`;
+ }
+ else if (result[0] == 'fail') {
+ // if fail, display 'x' (cross)
+ document.getElementById(`img-${result[1]}-status`).innerHTML = `close`;
+ }
+ else if (result[0] == 'done') {
+ progressBar.update(100, 'Upload Complete!');
+ clearBtn.disabled = false;
+ uploadBtn.disabled = false;
+ }
+ else if (result[0] == 'warning') {
+ // if warning, display triangle with exclamation point in it. This only occurs if you try
+ // to upload an image to an item that already has an image
+ document.getElementById(`img-${result[1]}-status`).innerHTML = `warning`;
+ }
+ else if (result[0] == 'total failure') {
+ finishedItems = imgsToUpload.length;
+ progressBar.update(100, 'Error occurred while attempting upload!');
+ document.getElementById('img-upload-status-text').innerHTML = `Upload Failed: ${result[1]}}`;
+ clearBtn.disabled = false;
+ uploadBtn.disabled = false;
+ }
+ if (result != 'done') {
+ finishedItems++;
+ }
+ // update progressbar when each image is uploaded/fails upload
+ progressBar.updateProgressBar(finishedItems * 100 / imgsToUpload.length);
+ });
+});
+// Other
+document.getElementById('load-item').addEventListener('click', loadItem);
+document.getElementById('valid-single').addEventListener('click', () => {
+ validSingle();
+});
+document.getElementById('valid-single-ext').addEventListener('click', () => {
+ validSingle(true);
+});
+document.getElementById('settings').addEventListener('click', openSettings);
+document.getElementById('topButton').addEventListener('click', toTop);
+document.getElementById('endButton').addEventListener('click', toEnd);
+document.getElementById('interactive').addEventListener('click', openExcel);
+document.getElementById('worksheet-path').addEventListener('click', openExcel);
+document.getElementById('pauseAuto').addEventListener('click', pauseAuto);
+document.getElementById('save-desc').addEventListener('click', writeDescription);
+document.getElementById('save-num').addEventListener('click', writeItemNum);
+document.getElementById('skip-row').addEventListener('click', skipRow);
+document.getElementById('continueAuto').addEventListener('click', continueAuto);
+document.getElementById('confirm-btn').addEventListener('click', () => {
+ uploadItem();
+});
+document.getElementById('upload-btn').addEventListener('click', () => {
+ const confirmModal = new bootstrap.Modal(document.getElementById('confirmModal'));
+ if (!(document.getElementById('maximo-desc').reportValidity() &&
+ document.getElementById('uom-field').reportValidity() &&
+ document.getElementById('com-group').reportValidity() &&
+ document.getElementById('gl-class').reportValidity())) {
+ return;
+ }
+ ItemAnalysis();
+ confirmModal.toggle();
+ getNextNumThenUpdate(document.getElementById('num-type').value);
+});
+// batch upload:
+document.getElementById('openBatchFile').addEventListener('click', () => {
+ openFile('worksheet-path');
+});
+document.getElementById('clear-batch-items-btn').addEventListener('click', () => {
+ document.getElementById('batch-items-table').innerHTML = ``;
+ document.getElementById('batch-copy-nums').disabled = true;
+ document.getElementById('batch-upload-status-text').innerHTML = 'Waiting for paste...';
+});
+document.getElementById('batch-copy-nums').addEventListener('click', () => {
+ try {
+ const result = getItemsFromTable('batch-items-table');
+ if (result == undefined || result == null || result == 0) {
+ throw ('Table missing columns');
+ }
+ const rows = parseInt(document.getElementById('batch-items-table').getAttribute('data-rows')) - 1;
+ let nums = '';
+ for (let i = 2; i <= rows + 1; i++) {
+ nums += document.getElementById(`${i}-${colLoc.maximo}`).innerHTML ? (document.getElementById(`${i}-${colLoc.maximo}`).innerHTML + '\n') : '';
+ }
+ navigator.clipboard.writeText(nums);
+ new Toast('Item Numbers Copied to Clipboard!');
+ }
+ catch (error) {
+ // console.log(error);
+ new Toast('Unable to copy numbers, please check table formatting!');
+ }
+});
+document.getElementById('batch-items-textinput').addEventListener('paste', (e) => {
+ setTimeout(() => {
+ const paste = e.target.value;
+ const table = document.getElementById('batch-items-table-div');
+ table.innerHTML = convertToTable(paste, 'batch-items-table');
+ document.getElementById('batch-copy-nums').disabled = false;
+ document.getElementById('batch-upload-status-text').innerHTML = 'Paste detected! Edit table if needed and click upload.';
+ e.target.value = '';
+ }, 0);
+});
+document.getElementById('batch-upload-btn').addEventListener('click', () => {
+ try {
+ itemsToUpload = getItemsFromTable('batch-items-table');
+ }
+ catch (error) {
+ itemsToUpload = [];
+ document.getElementById('batch-upload-status-text').innerHTML = `Error, check table format! (${error})`;
+ return;
+ }
+ if (itemsToUpload.length > 0) {
+ itemsToUpload.forEach((value, idx) => {
+ if (value) {
+ updateItemStatus('loading', idx + 1);
+ }
+ });
+ batchUploadItems(itemsToUpload);
+ return;
+ }
+ else {
+ document.getElementById('batch-upload-status-text').innerHTML = 'No valid items to upload!';
+ }
+ return;
+});
+document.getElementById('batch-paste-btn').addEventListener('click', () => __awaiter(void 0, void 0, void 0, function* () {
+ const text = yield navigator.clipboard.readText();
+ const pasteEvent = new Event('paste', { 'bubbles': true, 'cancelable': false });
+ const textinput = document.getElementById('batch-items-textinput');
+ textinput.value = text;
+ textinput.dispatchEvent(pasteEvent);
+}));
+document.getElementById('batch-copy-headers-btn').addEventListener('click', () => {
+ const copyText = `Maximo\tDescription\tIssue Unit\tCommodity Group\tGL Class\tSite\tStoreroom\tVendor\tCatalogue Number\n\t`;
+ navigator.clipboard.writeText(copyText);
+ new Toast('Table copied to clipboard!');
+});
+// dark theme toggle
+document.getElementById('dark-mode-switch').addEventListener('click', toggleTheme);
+// Infinite scroll
+// listener for enter key on search field
+document.getElementById('maximo-desc').addEventListener('keyup', function (event) {
+ // Number 13 is the "Enter" key on the keyboard
+ if (event.key === 'Enter') {
+ // Cancel the default action, if needed
+ event.preventDefault();
+ // Trigger the button element with a click
+ validSingle();
+ }
+});
+document.getElementById('interact-num').addEventListener('keyup', function (event) {
+ // Number 13 is the "Enter" key on the keyboard
+ if (event.key === 'Enter') {
+ // Cancel the default action, if needed
+ event.preventDefault();
+ // Trigger the button element with a click
+ loadItem();
+ }
+});
+function pauseAuto() {
+ document.getElementById('modeSelect').checked = true;
+}
+function loadItem() {
+ const itemnum = document.getElementById('interact-num').value.trim();
+ new Toast(`Loading Item: ${itemnum}`);
+ const worker = new WorkerHandler();
+ worker.work(['loadItem', itemnum], showItem);
+}
+function auto_grow(elementID) {
+ const element = document.getElementById(elementID);
+ element.style.height = '5px';
+ element.style.height = (element.scrollHeight) + 'px';
+}
+function showItem(data) {
+ document.getElementById('maximo-desc').value = data[0].description;
+ document.getElementById('uom-field').value = data[0].uom;
+ document.getElementById('com-group').value = data[0].commodity_group;
+ document.getElementById('gl-class').value = data[0].gl_class;
+}
+function writeDescription() {
+ const valid = new Validate();
+ const field = document.getElementById('maximo-desc');
+ if (field.value.length > 0) {
+ const bar = new ProgressBar();
+ bar.update(0, 'Writing asset description to file');
+ let desc = field.value.split(',');
+ desc = valid.assembleDescription(desc);
+ const params = worksheetParams();
+ params.outRow = document.getElementById('current-row').innerHTML;
+ const worker = new WorkerHandler();
+ worker.work(['writeDesc', [params, desc]], writeComplete);
+ }
+ else {
+ new Toast('Please enter a valid description');
+ }
+}
+function worksheetParams(path = false) {
+ const params = {
+ // input parameters
+ wsName: document.getElementById('ws-name').value || 'Sheet2', // name of ws
+ inDesc: (document.getElementById('input-col').value || 'F').toUpperCase().split(','), // description columns for input
+ startRow: document.getElementById('start-row').value || '2', // starting row of ws
+ // output parameters
+ outItemNum: document.getElementById('output-col').value.toUpperCase() || 'E',
+ outItemDesc: (document.getElementById('output-col-desc').value || 'F,G,H').toUpperCase().split(','),
+ outComm: document.getElementById('interact-num').value.toUpperCase() || 'I', // commodity group out
+ outGL: document.getElementById('interact-num').value.toUpperCase() || 'J', // gl class out
+ outUOM: document.getElementById('interact-num').value.toUpperCase() || 'K', // uom out
+ outQuestion: document.getElementById('interact-num').value.toUpperCase() || 'L', // questions out
+ outTranslate: document.getElementById('output-col-translation').value.toUpperCase() || 'L',
+ outMissing: document.getElementById('output-col-missing').value.toUpperCase() || 'K',
+ // output data
+ itemNum: document.getElementById('interact-num').value || '999TEST',
+ itemDesc: document.getElementById('maximo-desc').value || 'TEST,ITEM,DESCRIPTION',
+ commGroup: document.getElementById('com-group').value || '401', // commodity group in
+ glClass: document.getElementById('gl-class').value || '6200000000000', // gl class in
+ uom: document.getElementById('uom-field').value || 'EA', // uom in
+ };
+ if (path) {
+ params.filePath = path;
+ }
+ else {
+ params.filePath = document.getElementById('worksheet-path').value;
+ }
+ return params;
+}
+function writeItemNum() {
+ const num = document.getElementById('interact-num').value;
+ if (num.length > 0) {
+ const bar = new ProgressBar();
+ bar.update(0, 'Writing item number to file');
+ const path = document.getElementById('worksheet-path').value;
+ const wsName = document.getElementById('ws-name').value;
+ const rowNum = document.getElementById('current-row').innerHTML;
+ const cols = document.getElementById('output-col').value;
+ const worker = new WorkerHandler();
+ worker.work(['writeNum', [path, wsName, rowNum, cols, num]], writeComplete);
+ }
+ else {
+ new Toast('Please enter a valid item number');
+ }
+}
+function writeComplete() {
+ const rowNum = parseInt(document.getElementById('current-row').innerHTML);
+ new Toast(`Row ${rowNum} saved!`);
+ document.getElementById('interact-num').value = '';
+ interactiveGoNext(Number(rowNum) + 1);
+}
+function openFile(pathElement) {
+ const validFile = document.getElementById(pathElement);
+ const filePath = validFile.value;
+ if (filePath !== 'No file chosen') {
+ new Toast('Opening File in Excel!');
+ shell.openExternal(filePath);
+ }
+}
+// Deprecated function, unused.
+function openSettings() {
+ ipcRenderer.send('openSettings');
+ // sendsync blocks parent window...
+ // https://github.com/electron/electron/issues/10426
+}
+function openExcel() {
+ document.getElementById('input-col').value = document.getElementById('input-col').value.toUpperCase();
+ document.getElementById('output-col').value = document.getElementById('output-col').value.toUpperCase();
+ ipcRenderer.invoke('select-to-be-translated', 'finished').then((result) => {
+ if (!result.canceled) {
+ const worker = new WorkerHandler();
+ const params = worksheetParams(result.filePaths[0]);
+ worker.work(['interactive', params], finishLoadingBatch);
+ document.getElementById('worksheet-path').value = result.filePaths[0];
+ }
+ else {
+ new Toast('File Picker Cancelled');
+ }
+ });
+}
+// BATCH UPLOAD FUNCTIONS
+/**
+ * Reads a table and generates items from it
+ *
+ * @param {string} tableId the HTML id of the table to read
+ * @return {Array} an array of Items
+ */
+function getItemsFromTable(tableId) {
+ colLoc = {
+ description: -1,
+ uom: -1,
+ commGroup: -1,
+ glClass: -1,
+ maximo: -1,
+ vendor: -1,
+ storeroom: -1,
+ catNum: -1,
+ siteID: -1,
+ };
+ const table = document.getElementById(`${tableId}`);
+ // find Description, UOM, Commodity Group, and GL Class
+ const rows = parseInt(table.getAttribute('data-rows'));
+ const cols = parseInt(table.getAttribute('data-cols'));
+ // iniitalize items array
+ const items = [];
+ // go through first row to find headings.
+ let validParams = 0;
+ for (let i = 1; i <= cols; i++) {
+ // get a cell in the table by its id
+ const cell = document.getElementById('1-' + i);
+ // see if cell value matches any of the required parameters to create an item object
+ if (cell.innerHTML.toUpperCase() === 'DESCRIPTION') {
+ colLoc.description = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'UOM' || cell.innerHTML.toUpperCase() === 'ISSUE UNIT') {
+ colLoc.uom = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'COMMODITY GROUP' || cell.innerHTML.toUpperCase() === 'COMM GROUP') {
+ colLoc.commGroup = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'GL CLASS') {
+ colLoc.glClass = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'SITEID' || cell.innerHTML.toUpperCase() === 'SITE') {
+ colLoc.siteID = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'STOREROOM' || cell.innerHTML.toUpperCase() === 'STOREROOM') {
+ colLoc.storeroom = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'VENDOR' || cell.innerHTML.toUpperCase() === 'VENDOR NUMBER') {
+ colLoc.vendor = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'CAT NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOG NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOGUE NUMBER') {
+ colLoc.catNum = i;
+ validParams++;
+ }
+ else if (cell.innerHTML.toUpperCase() === 'MAXIMO' || cell.innerHTML.toUpperCase() === 'ITEM NUMBER') {
+ colLoc.maximo = i;
+ validParams++;
+ }
+ // console.log(validParams)
+ }
+ // Checking if mandatory columns are filled
+ if (colLoc.siteID != -1 || colLoc.storeroom != -1 || colLoc.vendor != -1 || colLoc.catNum != -1) {
+ if (colLoc.siteID == -1 || colLoc.storeroom == -1) {
+ let numMissing = 0;
+ let missingCols = '';
+ const missingColArr = [];
+ console.log('missing params');
+ for (const property in colLoc) {
+ if (colLoc[property] == -1 && property != 'vendor' && property != 'catNum') {
+ console.log(property);
+ numMissing++;
+ missingColArr.push(property.toLowerCase());
+ }
+ }
+ missingCols = missingColArr.join(', ');
+ document.getElementById('batch-upload-status-text').innerHTML = `Table is missing ${numMissing} column(s): (${missingCols}). Table will not be uploaded!`;
+ return;
+ }
+ }
+ else {
+ if (validParams < 5) {
+ let missingCols = '';
+ const missingColArr = [];
+ console.log('missing params');
+ for (const property in colLoc) {
+ if (colLoc[property] == -1 && property != 'siteID' && property != 'storeroom' && property != 'vendor' && property != 'catNum') {
+ console.log(property);
+ missingColArr.push(property.toLowerCase());
+ }
+ }
+ missingCols = missingColArr.join(', ');
+ document.getElementById('batch-upload-status-text').innerHTML = `Table is missing ${5 - validParams} column(s): (${missingCols}). Table will not be uploaded!`;
+ return;
+ }
+ }
+ let invalidItems = 0;
+ // Make item for request that includes inventory upload
+ if (validParams > 5) {
+ let site = undefined;
+ let storeroom = undefined;
+ let vendor = undefined;
+ let catNum = undefined;
+ for (let i = 2; i <= rows; i++) {
+ const desc = sanitizeString(document.getElementById(i + '-' + colLoc.description).innerHTML);
+ const uom = sanitizeString(document.getElementById(i + '-' + colLoc.uom).innerHTML).toUpperCase();
+ const commGroup = sanitizeString(document.getElementById(i + '-' + colLoc.commGroup).innerHTML);
+ const glclass = sanitizeString(document.getElementById(i + '-' + colLoc.glClass).innerHTML).toUpperCase();
+ if (colLoc.siteID != -1) {
+ site = sanitizeString(document.getElementById(i + '-' + colLoc.siteID).innerHTML).toUpperCase();
+ }
+ if (colLoc.storeroom != -1) {
+ storeroom = sanitizeString(document.getElementById(i + '-' + colLoc.storeroom).innerHTML).toUpperCase();
+ }
+ if (colLoc.vendor != -1) {
+ vendor = sanitizeString(document.getElementById(i + '-' + colLoc.vendor).innerHTML);
+ }
+ if (colLoc.catNum != -1) {
+ catNum = sanitizeString(document.getElementById(i + '-' + colLoc.catNum).innerHTML);
+ }
+ const maximo = sanitizeString(document.getElementById(i + '-' + colLoc.maximo).innerHTML);
+ // if all required parameters are not available, don't create the item and move to next row
+ if (desc == '' || uom == '' || commGroup == '' || glclass == '' || desc == 0 || uom == 0 || commGroup == 0 || glclass == 0 || site == '' || storeroom == '') {
+ updateItemStatus('error', (i - 1));
+ items.push('');
+ invalidItems++;
+ continue;
+ }
+ const item = new Item(undefined, desc, uom, commGroup, glclass, site, storeroom, vendor, catNum);
+ if (colLoc.maximo != -1 && maximo != 0 && maximo.toString().length === 7) {
+ item.itemnumber = maximo;
+ }
+ else if (desc.toUpperCase().includes('DWG')) {
+ item.series = 98;
+ }
+ else if (commGroup == '490' && glclass == 'PLS') {
+ // Change when when item num reachs 9920000
+ item.series = 991;
+ }
+ // console.log(item);
+ // add the item to the array
+ items.push(item);
+ }
+ }
+ // Make item for request that doesn't need inventory upload
+ else {
+ for (let i = 2; i <= rows; i++) {
+ const desc = sanitizeString(document.getElementById(i + '-' + colLoc.description).innerHTML);
+ const uom = sanitizeString(document.getElementById(i + '-' + colLoc.uom).innerHTML).toUpperCase();
+ const commGroup = sanitizeString(document.getElementById(i + '-' + colLoc.commGroup).innerHTML);
+ const glclass = sanitizeString(document.getElementById(i + '-' + colLoc.glClass).innerHTML).toUpperCase();
+ const maximo = sanitizeString(document.getElementById(i + '-' + colLoc.maximo).innerHTML);
+ // if all required parameters are not available, don't create the item and move to next row
+ if (desc == '' || uom == '' || commGroup == '' || glclass == '' || desc == 0 || uom == 0 || commGroup == 0 || glclass == 0) {
+ updateItemStatus('error', (i - 1));
+ items.push('');
+ invalidItems++;
+ continue;
+ }
+ const item = new Item(undefined, desc, uom, commGroup, glclass);
+ if (colLoc.maximo != -1 && maximo != 0 && maximo.toString().length === 7) {
+ item.itemnumber = maximo;
+ }
+ else if (desc.toUpperCase().includes('DWG')) {
+ item.series = 98;
+ }
+ else if (commGroup == '490' && glclass == 'PLS') {
+ // Change when when item num reachs 9920000
+ item.series = 991;
+ }
+ // console.log(item);
+ // add the item to the array
+ items.push(item);
+ }
+ }
+ if (invalidItems > 0) {
+ document.getElementById('batch-upload-status-text').innerHTML = `Warning! ${invalidItems} invalid items will not be uploaded`;
+ }
+ // return the item array
+ return items;
+}
+/**
+ * Uploads an item from item information accordion dropdown (single item upload)
+ *
+ */
+function uploadItem() {
+ return __awaiter(this, void 0, void 0, function* () {
+ document.getElementById('confirm-btn').innerHTML = ' Uploading...';
+ document.getElementById('confirm-btn').disabled = true;
+ const worker = new WorkerHandler();
+ const item = new Item(sanitizeString(document.getElementById('interact-num').value), sanitizeString(document.getElementById('maximo-desc').value), sanitizeString(document.getElementById('uom-field').value), sanitizeString(document.getElementById('com-group').value), sanitizeString(document.getElementById('gl-class').value));
+ if (document.getElementById('long-desc').value.length > 0) {
+ item.longdescription = document.getElementById('long-desc').value;
+ }
+ worker.work(['uploadItems', [item]], (e) => {
+ console.log(e);
+ if (e === undefined || typeof e != 'string' || e == 200) {
+ document.getElementById('error').innerHTML = 'Upload Success';
+ document.getElementById('confirm-btn').innerHTML = 'Upload Item';
+ document.getElementById('confirm-btn').disabled = false;
+ new Toast('Upload Complete!', 'bg-success');
+ const itemUrl = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${item.itemnumber}`;
+ document.getElementById('error').innerHTML = `Item Upload Successful! (Click to view item) `;
+ document.getElementById('item-link').addEventListener('click', function (x) {
+ x.preventDefault();
+ shell.openExternal(itemUrl);
+ });
+ }
+ else {
+ document.getElementById('error').innerHTML = 'Upload Fail';
+ document.getElementById('confirm-btn').innerHTML = 'Upload Item';
+ document.getElementById('confirm-btn').disabled = false;
+ // TODO: fail messages
+ document.getElementById('error').innerHTML = `Item Upload Failed! ${e}`;
+ }
+ });
+ });
+}
+/**
+ * Uploads an array of items
+ *
+ * @param {Array} items
+ */
+function batchUploadItems(items) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const worker = new WorkerHandler();
+ // disable clear and upload buttons while uploading items to prevent duplicate requests
+ const btn = document.getElementById('batch-upload-btn');
+ const clearBtn = document.getElementById('clear-batch-items-btn');
+ clearBtn.disabled = true;
+ btn.disabled = true;
+ worker.work(['uploadItems', items, true], (e) => {
+ let finishText = `Upload Finished! ${e[2]} items uploaded, ${e[3]} items added to inventory. `;
+ if (e[0] == 'failure') {
+ new Toast(`Invalid! ${e[1]}}!`);
+ }
+ clearBtn.disabled = false;
+ btn.disabled = false;
+ updateItemNums(e[0]);
+ const rows = parseInt(document.getElementById('batch-items-table').getAttribute('data-rows')) - 1;
+ let nums = '';
+ for (let i = 2; i <= rows + 1; i++) {
+ nums += document.getElementById(`${i}-${colLoc.maximo}`).innerHTML ? (document.getElementById(`${i}-${colLoc.maximo}`).innerHTML + ',') : '';
+ }
+ if (e[2] > 0) {
+ const itemUrl = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${nums}`;
+ finishText += `Click to view:`;
+ document.getElementById('batch-upload-status-text').innerHTML = finishText;
+ document.getElementById('batch-link').addEventListener('click', function (e) {
+ e.preventDefault();
+ shell.openExternal(itemUrl);
+ });
+ }
+ else {
+ document.getElementById('batch-upload-status-text').innerHTML = finishText;
+ }
+ console.log('upload finished');
+ });
+ });
+}
+/**
+ * Gets a list of newly generated item nums and updates the table with them.
+ *
+ * If an item has just been uploaded, populates item num cell with new number.
+ *
+ * @param {int[][]} arr array of pairs of item nums and table row indexes
+ */
+function updateItemNums(arr) {
+ for (const pair of arr) {
+ const itemNum = pair[0];
+ const itemRowIndex = pair[1];
+ // update item number cell
+ const cell = document.getElementById(`${itemRowIndex + 1}-${colLoc.maximo}`);
+ cell.innerHTML = itemNum;
+ // highlight the item number yellow to signify that it was newly uploaded
+ cell.classList.add('table-alert');
+ }
+}
+// //////////////////////
+function skipRow() {
+ const row = document.getElementById('current-row').innerHTML;
+ interactiveGoNext(Number(row) + 1);
+}
+function finishLoadingBatch(params) {
+ const bar = new ProgressBar();
+ // this has a special work thread since initializing a worker thread takes ~700 ms which is too long
+ document.getElementById('valid-row').innerHTML = params[1];
+ document.getElementById('total-row').innerHTML = params[2];
+ const worker = new Worker('./worker.js');
+ const db = new Database();
+ let description = db.getDescription(params[0]);
+ if (description === undefined) {
+ bar.update(100, 'Done!');
+ worker.terminate();
+ new Toast('Finished Batch Processing');
+ return false;
+ }
+ bar.update(0, 'Processing Descriptions');
+ processBatch(worker, params[0], description);
+ worker.onmessage = (msg) => {
+ if (msg.data[0] === 'nextrow') {
+ description = db.getDescription(msg.data[1]);
+ if (description === undefined) {
+ params = worksheetParams(document.getElementById('worksheet-path').value);
+ worker.postMessage([
+ 'saveProcessed',
+ [params, msg.data[1]],
+ ]);
+ new Toast('Finished Batch Processing');
+ new Toast('Please wait for file to finish saving...');
+ return false;
+ }
+ document.getElementById('current-row').innerHTML = description.row;
+ bar.update(msg.data[1] / params[2] * 100, `Processing Description. Row: ${msg.data[1]} of ${params[2]}`);
+ processBatch(worker, msg.data[1], description);
+ }
+ else if (msg.data[0] === 'saveComplete') {
+ interactiveGoNext(msg.data[1]);
+ new Toast('File Saved');
+ worker.terminate();
+ }
+ else {
+ console.log(`IDK: ${msg.data}`);
+ }
+ };
+}
+function processBatch(worker, row, description) {
+ const interactive = document.getElementById('modeSelect').checked;
+ const related = document.getElementById('relatedSelect').checked;
+ const translate = document.getElementById('translateSelect').checked;
+ const params = worksheetParams(document.getElementById('worksheet-path').value);
+ if (interactive) {
+ new Toast('Pausing / Switching to Interactive Mode');
+ worker.postMessage([
+ 'saveProcessed',
+ [params, row],
+ ]);
+ }
+ else {
+ worker.postMessage([
+ 'nonInteractive',
+ [
+ related,
+ translate,
+ description.description,
+ document.getElementById('selected-language').value,
+ params,
+ row,
+ ],
+ ]);
+ }
+}
+function continueAuto() {
+ document.getElementById('modeSelect').checked = false;
+ finishLoadingBatch([
+ Number(document.getElementById('current-row').innerHTML),
+ document.getElementById('valid-row').innerHTML,
+ document.getElementById('total-row').innerHTML,
+ ]);
+}
+function interactiveGoNext(row) {
+ const bar = new ProgressBar();
+ const db = new Database();
+ const description = db.getDescription(row);
+ if (description === undefined) {
+ bar.update(100, 'Done!');
+ new Toast('End of File Reached');
+ return false;
+ }
+ document.getElementById('current-row').innerHTML = description.row;
+ if (description) {
+ const worker = new WorkerHandler();
+ document.getElementById('maximo-desc').value = description.description;
+ worker.work(['validSingle', description.description], showResult);
+ }
+ else {
+ const field = document.getElementById('maximo-desc');
+ field.placeholder = 'Row is blank, press skip row to go next';
+ field.value = '';
+ const bar = new ProgressBar();
+ bar.update(100, 'Done');
+ }
+}
+function validSingle(isExtended = false) {
+ const bar = new ProgressBar();
+ bar.update(0, 'Starting Item Description Validation');
+ const raw_desc = document.getElementById('maximo-desc').value;
+ const worker = new WorkerHandler();
+ worker.work(['validSingle', raw_desc], (result) => {
+ showResult(result, isExtended);
+ });
+}
+function showResult(result, isExtended = false) {
+ let triDesc = document.getElementById('result-triple-main');
+ triDesc.value = result[0][0];
+ triDesc = document.getElementById('result-triple-ext1');
+ triDesc.value = result[0][1];
+ triDesc = document.getElementById('result-triple-ext2');
+ triDesc.value = result[0][2];
+ const related = document.getElementById('relatedSelect').checked;
+ const translate = document.getElementById('translateSelect').checked;
+ calcConfidence(result[0][3]);
+ document.getElementById('validate-badge').innerHTML = 'New';
+ if (translate) {
+ translationDescription(result[0][3]);
+ }
+ if (related) {
+ findRelated(result[0], isExtended);
+ }
+}
+function ItemAnalysis() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const valid = new Validate();
+ const raw_desc = document.getElementById('maximo-desc').value;
+ const result = yield valid.validateSingle(raw_desc);
+ let triDesc = document.getElementById('result-triple-main');
+ triDesc.value = result[0];
+ triDesc = document.getElementById('result-triple-ext1');
+ triDesc.value = result[1];
+ triDesc = document.getElementById('result-triple-ext2');
+ triDesc.value = result[2];
+ calcConfidence(result[3]);
+ });
+}
+function findRelated(result, isExtended = false) {
+ const worker = new WorkerHandler();
+ worker.work(['findRelated', result[3], isExtended], (result) => {
+ showRelated(result, isExtended);
+ });
+}
+function translationDescription(description) {
+ // for now do not translate if english is selected
+ if (document.getElementById('selected-language').value != 'en') {
+ const worker = new WorkerHandler();
+ if (document.getElementById('result-triple-ext1').value) {
+ description = `${document.getElementById('result-triple-main').value},${document.getElementById('result-triple-ext1').value}`;
+ }
+ else {
+ description = document.getElementById('result-triple-main').value;
+ }
+ worker.work([
+ 'translateItem',
+ description,
+ document.getElementById('selected-language').value,
+ 'post',
+ ], displayTranslation);
+ }
+ else {
+ new Toast('Currently translation into English is not supported');
+ }
+}
+function displayTranslation(data) {
+ document.getElementById('trans-desc').value = data[0];
+ document.getElementById('translation-description').value = `The following words do not have a translation:\n${data[1]}\nPlease check logs at bottom of page for details`;
+ auto_grow('translation-description');
+}
+function calcConfidence(data) {
+ let description;
+ let level = 0;
+ let tree = '';
+ let parent = 0;
+ const regex = /\d+/g;
+ const db = new Database();
+ let analysis;
+ let result = '';
+ const option = {
+ style: 'percent',
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1,
+ };
+ const formatter = new Intl.NumberFormat('en-US', option);
+ if ((data === null || data === void 0 ? void 0 : data.length) > 0) { // test if description is blank
+ description = data.split(',');
+ for (let j = 0; j < description.length; j++) {
+ if (!(description[j].match(regex))) {
+ if (db.isManufacturer(description[j])) {
+ result = `${result}\n${description[j]} is confirmed as a manufacturer`;
+ }
+ else {
+ level++;
+ if (level == 1) {
+ tree = description[j];
+ }
+ else {
+ tree = tree + ',' + description[j];
+ }
+ analysis = db.getAnalysis(tree);
+ if (analysis) {
+ if (level == 1) {
+ if (analysis.count >= 100) {
+ result = `${description[j]}: is COMMONLY used as an Item Type.\n${analysis.count}: occurrences found`;
+ }
+ else if (analysis.count >= 20) {
+ result = `${description[j]}: is SOMETIMES used as an Item Type.\n${analysis.count}: occurrences found`;
+ }
+ else {
+ result = `WARNING: ${description[j]}: is an UNCOMMON Item Type.\nPlease double check.\n${analysis.count}: occurrences found`;
+ }
+ }
+ else {
+ if (analysis.count / parent >= 0.25) {
+ result = `${result}\n${description[j]} is COMMONLY used as an item descriptor for ${tree}.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
+ }
+ else if (analysis.count / parent >= 0.05) {
+ result = `${result}\n${description[j]} is SOMETIMES used as an item descriptor for ${tree}.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
+ }
+ else {
+ result = `${result}\n${description[j]} is an UNCOMMON item descriptor for ${tree}.\nPlease double check.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
+ }
+ }
+ parent = analysis.count;
+ }
+ else {
+ result = `${result}\n${description[j]}: Does not exist in Maximo as part of: ${tree}.\nPlease Check with Corporate`;
+ }
+ }
+ }
+ }
+ document.getElementById('valid-description').value = result.trim();
+ }
+ else {
+ new Toast('Blank Description');
+ }
+}
+/**
+ * Initializes search results table and populates relatedResults object
+ * with search results.
+ *
+ * @param {Array< Map>,Map,String>} result array of [array of item nums of all search results, map of item nums to descriptions, and search query with words separated by commas]
+ * @param {bool} isExtended whether the user has clicked extended search
+ */
+function showRelated(result, isExtended = false) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const bar = new ProgressBar();
+ if (!result[0]) {
+ bar.update(100, 'Done!');
+ return false;
+ }
+ // reverse results to get newest items first.
+ // technically this isn't the best way to do
+ // this because results aren't guaranteed
+ // to be in order of oldest to newest.
+ for (const [key, value] of Object.entries(result[0])) {
+ result[0][key] = result[0][key].reverse();
+ }
+ // populate global variable with search results (bad practice, but it works)
+ relatedResults = {
+ idx: 0,
+ curKey: 0,
+ results: result,
+ };
+ // reset table after called
+ const relatedTable = document.getElementById('related-table');
+ const numResultsText = document.getElementById('num-results');
+ if (isExtended) {
+ relatedTable.classList.add(`isExt`);
+ }
+ else {
+ if (relatedTable.classList.contains(`isExt`)) {
+ relatedTable.classList.remove(`isExt`);
+ }
+ }
+ // Add headings to the search results table
+ relatedTable.innerHTML = `
+
+
+
+
Percent Match
+
Item Number
+
Item Description
+ ${(isExtended ? '
More Info
' : '')}
+
UOM
+
C_Group
+
GL_Class
+
+
+
+
+
+ `;
+ numResultsText.innerHTML = `Found ${Object.entries(result[1]).length} results`;
+ // expand the search results accordion
+ document.getElementById('related-items-accordion-btn').classList.remove('collapsed');
+ // load a couple of items
+ loadRelated();
+ html = new bootstrap.Collapse(document.getElementById('accordion-relatedItem'), { toggle: false });
+ html.show();
+ bar.update(100, 'Done!');
+ });
+}
+function loadRelated() {
+ var _a;
+ // check if user clicked extended search
+ const isExtended = document.getElementById('related-table').classList.contains('isExt');
+ // a map with percent match as key (in decimal form) and array of items as value
+ // for example, if the key is 1, the list of items match with the search query 100%. If the key is 0.5, the list of items match with the search query 50%.
+ const scores = relatedResults.results[0];
+ // relatedResults.idx is like a bookmark. It keeps track of how many items have been loaded from the array of items associated with the current key in scores.
+ // relatedResults.curKey is the number of the current key that is being loaded if you were to iterate thru the keys in scores.
+ // For example, the first key's number would be 0, second key 1, etc.
+ if (relatedResults.curKey >= Object.entries(scores).length) {
+ // If curKey is equal or larger than the number of keys in scores, then there are no more items to load, so return
+ return;
+ }
+ else if (Object.entries(scores)[relatedResults.curKey][1].length == 0) {
+ // If there are no items associated with the current key, then move to the next key and try loading items again.
+ relatedResults.curKey++; // increment curKey so that the next time the function runs, it will try to load items from the next key
+ relatedResults.idx = 0; // reset idx so that it starts from the beginning of the array
+ loadRelated();
+ return; // return so we don't do make an infinite loop
+ }
+ const step = 20; // number of items to load at once.
+ // get arrs from results obj
+ const itemNames = relatedResults.results[1]; // a map with the 9-series number of the item as the key and the item info as value. Item info is an array with 4 items: [description, gl class, uom, commodity group]
+ const searchWords = relatedResults.results[2].split(','); // an array of the search query split by commas. For example, if the search query is "test, item, description", then searchWords would be ["test", "item", "description"]
+ let html = '';
+ let color = ''; // html is the html that will be added to the search results table. color is the color of the row in the table.
+ let itemDescription;
+ // formatting options for percent match. Converts decimal to percent and rounds to nearest whole number.
+ const option = {
+ style: 'percent',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+ const formatter = new Intl.NumberFormat('en-US', option);
+ // technically this is bad practise since object order might not be guarenteed
+ // https://stackoverflow.com/questions/983267/how-to-access-the-first-property-of-a-javascript-object
+ const percentMatch = Object.entries(scores)[relatedResults.curKey][0]; // get the percent match (name of the current key)
+ const itemNumList = Object.entries(scores)[relatedResults.curKey][1]; // get the array of items associated with key
+ let itemsToLoad; // array of items to load
+ if (relatedResults.idx + step >= itemNumList.length) {
+ // if there are less than 20 items to load, load the remaining items in value and increment curKey and reset idx
+ // this way, the next time the function is called, the next key will be loaded instead
+ itemsToLoad = itemNumList.slice(relatedResults.idx, undefined); // get array of items from idx to end of array
+ relatedResults.curKey++;
+ relatedResults.idx = 0;
+ }
+ else {
+ itemsToLoad = itemNumList.slice(relatedResults.idx, relatedResults.idx + step);
+ relatedResults.idx += step;
+ }
+ // iterate thru each item in value array
+ for (const itemNum of itemsToLoad) {
+ itemDescription = itemNames[itemNum][0];
+ if (itemDescription) {
+ // Bold all words in item description that match the search query
+ for (const word of searchWords) {
+ split = word.split(' ');
+ for (const smallWord of split) {
+ if (smallWord.length > 0) {
+ itemDescription = itemDescription.replace(new RegExp(`${smallWord}`, 'i'), `${(_a = itemDescription.match(new RegExp(`${smallWord}`, 'i'))) === null || _a === void 0 ? void 0 : _a[0]}`);
+ }
+ }
+ }
+ // set row color based on percent match
+ if (percentMatch > 0.7) {
+ color = 'table-success'; // green
+ }
+ else if (percentMatch > 0.4) {
+ color = 'table-warning'; // yellow
+ }
+ else {
+ color = 'table-danger'; // red
+ }
+ // create HTML row.
+ // In extended search, the vendor info is split from the item description by a | (pipe character).
+ // All info after the pipe character is put into another column.
+ // If the item description does not have a pipe character, then the second column is not loaded.
+ html = `${html}\n
`;
- }
-
- //generate a link to open items that are being uploaded to in maximo
-
- let url = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${nums}`;
- document.getElementById("img-upload-status-text").innerHTML = `Selected Items:`;
- document.getElementById("imgs-link").addEventListener('click', function (e) {
- e.preventDefault();
- shell.openExternal(url);
- });
-
- progressBar.update(100, 'Ready to Upload!');
-});
-
-//clear the file picker each time it is clicked
-document.getElementById("imgInput").addEventListener("click", () => {
- document.getElementById("img-clear-btn").dispatchEvent(new Event('click'));
-});
-
-document.getElementById("img-clear-btn").addEventListener("click", () => {
- //reset all related components
- let progressBar = new ProgressBar();
- document.getElementById("imgList").innerHTML = ``;
- progressBar.update(100, 'Ready!');
- //empty list of images to upload
- imgsToUpload = [];
- //empty file picker
- document.getElementById("imgInput").value = null;
-
- document.getElementById("img-upload-status-text").innerHTML = 'Select Images to Continue...'
-});
-document.getElementById("img-upload-btn").addEventListener("click", () => {
- let progressBar = new ProgressBar();
-
- let clearBtn = document.getElementById('img-clear-btn');
- let uploadBtn = document.getElementById('img-upload-btn');
-
- //return if user has picked no images
- if (imgsToUpload.length == 0) {
- new Toast('No Images Selected!');
- return;
- }
-
- let finishedItems = 0;
-
- //disable the clear and upload buttons while upload is taking place so the
- //user can't send duplicate requests or accidentally clear the image upload list
- //while its uploading
- clearBtn.disabled = true;
- uploadBtn.disabled = true;
-
- const worker = new WorkerHandler();
-
- progressBar.update(0, 'Uploading Images...');
-
- //upload all images and update UI
- worker.work(['uploadImages', imgsToUpload], (result) => {
- if (result[0] == 'success') {
- //if success, display checkmark
- document.getElementById(`img-${result[1]}-status`).innerHTML = `done`;
- } else if (result[0] == 'fail') {
- //if fail, display 'x' (cross)
- document.getElementById(`img-${result[1]}-status`).innerHTML = `close`;
- } else if (result[0] == 'done') {
- progressBar.update(100, 'Upload Complete!');
- clearBtn.disabled = false;
- uploadBtn.disabled = false;
- } else if (result[0] == 'warning') {
- //if warning, display triangle with exclamation point in it. This only occurs if you try
- //to upload an image to an item that already has an image
- document.getElementById(`img-${result[1]}-status`).innerHTML = `warning`;
- } else if (result[0] == 'total failure') {
- finishedItems = imgsToUpload.length;
- progressBar.update(100, 'Error occurred while attempting upload!');
- document.getElementById("img-upload-status-text").innerHTML = `Upload Failed: ${result[1]}}`;
- clearBtn.disabled = false;
- uploadBtn.disabled = false;
- }
-
- if (result != 'done') {
- finishedItems++;
- }
-
- //update progressbar when each image is uploaded/fails upload
- progressBar.updateProgressBar(finishedItems * 100 / imgsToUpload.length);
- });
-});
-
-//Other
-document.getElementById("load-item").addEventListener("click", loadItem);
-document.getElementById("valid-single").addEventListener("click", () => { validSingle() });
-document.getElementById("valid-single-ext").addEventListener("click", () => { validSingle(true) });
-document.getElementById("settings").addEventListener("click", openSettings);
-document.getElementById("topButton").addEventListener("click", toTop);
-document.getElementById("endButton").addEventListener("click", toEnd);
-document.getElementById("interactive").addEventListener("click", openExcel);
-document.getElementById("worksheet-path").addEventListener("click", openExcel);
-document.getElementById("pauseAuto").addEventListener("click", pauseAuto);
-
-document.getElementById("save-desc").addEventListener("click", writeDescription);
-document.getElementById("save-num").addEventListener("click", writeItemNum);
-document.getElementById("skip-row").addEventListener("click", skipRow);
-document.getElementById("continueAuto").addEventListener("click", continueAuto);
-document.getElementById("confirm-btn").addEventListener("click", () => { uploadItem(); });
-document.getElementById("upload-btn").addEventListener("click", () => {
-
- let confirmModal = new bootstrap.Modal(document.getElementById("confirmModal"));
- if (!(
- document.getElementById("maximo-desc").reportValidity() &&
- document.getElementById("uom-field").reportValidity() &&
- document.getElementById("com-group").reportValidity() &&
- document.getElementById("gl-class").reportValidity()
- )) {
- return;
- }
- ItemAnalysis();
- confirmModal.toggle();
- getNextNumThenUpdate(document.getElementById("num-type").value);
-});
-
-
-//batch upload:
-document.getElementById("openBatchFile").addEventListener("click", () => { openFile("worksheet-path") });
-
-document.getElementById("clear-batch-items-btn").addEventListener("click", () => {
- document.getElementById("batch-items-table").innerHTML = ``;
- document.getElementById("batch-copy-nums").disabled = true;
- document.getElementById("batch-upload-status-text").innerHTML = 'Waiting for paste...';
-})
-
-document.getElementById("batch-copy-nums").addEventListener("click", () => {
- try {
- let result = getItemsFromTable("batch-items-table");
- if (result == undefined || result == null || result == 0) {
- throw ('Table missing columns');
- }
- let rows = parseInt(document.getElementById("batch-items-table").getAttribute("data-rows")) - 1;
- let nums = "";
- for (let i = 2; i <= rows + 1; i++) {
- nums += document.getElementById(`${i}-${colLoc.maximo}`).innerHTML ? (document.getElementById(`${i}-${colLoc.maximo}`).innerHTML + "\n") : "";
- }
- navigator.clipboard.writeText(nums);
- new Toast('Item Numbers Copied to Clipboard!');
- } catch (error) {
- //console.log(error);
- new Toast('Unable to copy numbers, please check table formatting!');
- }
-
-});
-
-document.getElementById("batch-items-textinput").addEventListener("paste", (e) => {
- setTimeout(() => {
- let paste = e.target.value;
- let table = document.getElementById("batch-items-table-div");
- table.innerHTML = convertToTable(paste, "batch-items-table");
-
- document.getElementById("batch-copy-nums").disabled = false;
-
- document.getElementById("batch-upload-status-text").innerHTML = 'Paste detected! Edit table if needed and click upload.';
- e.target.value = "";
- }, 0)
-})
-document.getElementById("batch-upload-btn").addEventListener("click", () => {
- try {
- itemsToUpload = getItemsFromTable("batch-items-table")
- } catch (error) {
- itemsToUpload = [];
- document.getElementById("batch-upload-status-text").innerHTML = `Error, check table format! (${error})`;
- return;
- }
-
- if (itemsToUpload.length > 0) {
- itemsToUpload.forEach((value, idx) => {
- if (value) {
- updateItemStatus('loading', idx + 1);
- }
- })
- batchUploadItems(itemsToUpload);
- return;
- } else {
- document.getElementById("batch-upload-status-text").innerHTML = 'No valid items to upload!';
- }
-
- return;
-})
-document.getElementById("batch-paste-btn").addEventListener("click", async () => {
- const text = await navigator.clipboard.readText();
- const pasteEvent = new Event("paste", { "bubbles": true, "cancelable": false });
- let textinput = document.getElementById("batch-items-textinput");
-
- textinput.value = text;
- textinput.dispatchEvent(pasteEvent);
-})
-document.getElementById("batch-copy-headers-btn").addEventListener("click", () => {
- let copyText = `Maximo\tDescription\tIssue Unit\tCommodity Group\tGL Class\tSite\tStoreroom\tVendor\tCatalogue Number\n\t`;
- navigator.clipboard.writeText(copyText);
- new Toast('Table copied to clipboard!');
-})
-//dark theme toggle
-document.getElementById("dark-mode-switch").addEventListener("click", toggleTheme);
-//Infinite scroll
-
-// listener for enter key on search field
-document.getElementById("maximo-desc").addEventListener("keyup", function (event) {
- // Number 13 is the "Enter" key on the keyboard
- if (event.key === "Enter") {
- // Cancel the default action, if needed
- event.preventDefault();
- // Trigger the button element with a click
- validSingle();
- }
-});
-
-document.getElementById("interact-num").addEventListener("keyup", function (event) {
- // Number 13 is the "Enter" key on the keyboard
- if (event.key === "Enter") {
- // Cancel the default action, if needed
- event.preventDefault();
- // Trigger the button element with a click
- loadItem();
- }
-});
-
-function pauseAuto() {
- document.getElementById("modeSelect").checked = true;
-}
-
-function loadItem() {
- var itemnum = document.getElementById("interact-num").value.trim();
- new Toast(`Loading Item: ${itemnum}`);
- const worker = new WorkerHandler();
- worker.work(['loadItem', itemnum], showItem);
-}
-
-function auto_grow(elementID) {
- const element = document.getElementById(elementID);
- element.style.height = "5px";
- element.style.height = (element.scrollHeight) + "px";
-}
-
-function showItem(data) {
- document.getElementById("maximo-desc").value = data[0].description;
- document.getElementById("uom-field").value = data[0].uom;
- document.getElementById("com-group").value = data[0].commodity_group;
- document.getElementById("gl-class").value = data[0].gl_class;
-}
-
-function writeDescription() {
- const valid = new Validate();
- let field = document.getElementById("maximo-desc");
- if (field.value.length > 0) {
- let bar = new ProgressBar();
- bar.update(0, 'Writing asset description to file');
- let desc = field.value.split(',');
- desc = valid.assembleDescription(desc);
- let params = worksheetParams();
- params.outRow = document.getElementById("current-row").innerHTML;
- const worker = new WorkerHandler();
- worker.work(['writeDesc', [params, desc]], writeComplete);
- } else {
- new Toast('Please enter a valid description');
- }
-}
-
-function worksheetParams(path = false) {
- let params = {
- // input parameters
- wsName: document.getElementById("ws-name").value || "Sheet2", // name of ws
- inDesc: (document.getElementById("input-col").value || "F").toUpperCase().split(','), // description columns for input
- startRow: document.getElementById("start-row").value || "2", // starting row of ws
- // output parameters
- outItemNum: document.getElementById("output-col").value.toUpperCase() || "E",
- outItemDesc: (document.getElementById("output-col-desc").value || "F,G,H").toUpperCase().split(','),
- outComm: document.getElementById("interact-num").value.toUpperCase() || "I", // commodity group out
- outGL: document.getElementById("interact-num").value.toUpperCase() || "J", // gl class out
- outUOM: document.getElementById("interact-num").value.toUpperCase() || "K", // uom out
- outQuestion: document.getElementById("interact-num").value.toUpperCase() || "L", // questions out
- outTranslate: document.getElementById("output-col-translation").value.toUpperCase() || "L",
- outMissing: document.getElementById("output-col-missing").value.toUpperCase() || "K",
- // output data
- itemNum: document.getElementById("interact-num").value || '999TEST',
- itemDesc: document.getElementById("maximo-desc").value || "TEST,ITEM,DESCRIPTION",
- commGroup: document.getElementById("com-group").value || "401", // commodity group in
- glClass: document.getElementById("gl-class").value || "6200000000000", //gl class in
- uom: document.getElementById("uom-field").value || "EA", // uom in
- };
- if (path) {
- params.filePath = path;
- } else {
- params.filePath = document.getElementById("worksheet-path").value;
- }
- return params;
-}
-
-function writeItemNum() {
- let num = document.getElementById("interact-num").value;
- if (num.length > 0) {
- let bar = new ProgressBar();
- bar.update(0, 'Writing item number to file');
- let path = document.getElementById("worksheet-path").value;
- let wsName = document.getElementById("ws-name").value;
- let rowNum = document.getElementById("current-row").innerHTML;
- let cols = document.getElementById("output-col").value;
- const worker = new WorkerHandler();
- worker.work(['writeNum', [path, wsName, rowNum, cols, num]], writeComplete);
- } else {
- new Toast('Please enter a valid item number');
- }
-}
-
-function writeComplete() {
- let rowNum = parseInt(document.getElementById("current-row").innerHTML);
- new Toast(`Row ${rowNum} saved!`);
- document.getElementById("interact-num").value = '';
- interactiveGoNext(Number(rowNum) + 1);
-}
-
-function openFile(pathElement) {
- const validFile = document.getElementById(pathElement);
- const filePath = validFile.value;
- if (filePath !== 'No file chosen') {
- new Toast('Opening File in Excel!');
- shell.openExternal(filePath);
- }
-}
-
-//Deprecated function, unused.
-function openSettings() {
- ipcRenderer.send('openSettings');
- //sendsync blocks parent window...
- //https://github.com/electron/electron/issues/10426
-}
-
-function openExcel() {
- document.getElementById("input-col").value = document.getElementById("input-col").value.toUpperCase();
- document.getElementById("output-col").value = document.getElementById("output-col").value.toUpperCase();
-
- ipcRenderer.invoke('select-to-be-translated', 'finished').then((result) => {
- if (!result.canceled) {
- const worker = new WorkerHandler();
- const params = worksheetParams(result.filePaths[0]);
- worker.work(['interactive', params], finishLoadingBatch);
- document.getElementById("worksheet-path").value = result.filePaths[0];
- } else {
- new Toast('File Picker Cancelled');
- }
- });
-}
-
-//BATCH UPLOAD FUNCTIONS
-/**
- * Reads a table and generates items from it
- *
- * @param {string} tableId the HTML id of the table to read
- * @returns {Array} an array of Items
- */
-function getItemsFromTable(tableId) {
- colLoc = {
- description: -1,
- uom: -1,
- commGroup: -1,
- glClass: -1,
- maximo: -1,
- vendor: -1,
- storeroom: -1,
- catNum: -1,
- siteID: -1,
- }
-
- let table = document.getElementById(`${tableId}`);
- //find Description, UOM, Commodity Group, and GL Class
- let rows = parseInt(table.getAttribute("data-rows"));
- let cols = parseInt(table.getAttribute("data-cols"));
- //iniitalize items array
- let items = [];
- //go through first row to find headings.
- let validParams = 0;
- for (let i = 1; i <= cols; i++) {
- //get a cell in the table by its id
- let cell = document.getElementById("1-" + i);
-
- //see if cell value matches any of the required parameters to create an item object
- if (cell.innerHTML.toUpperCase() === 'DESCRIPTION') {
- colLoc.description = i;
- validParams++;
- } else if (cell.innerHTML.toUpperCase() === 'UOM' || cell.innerHTML.toUpperCase() === 'ISSUE UNIT') {
- colLoc.uom = i;
- validParams++;
- } else if (cell.innerHTML.toUpperCase() === 'COMMODITY GROUP' || cell.innerHTML.toUpperCase() === 'COMM GROUP') {
- colLoc.commGroup = i;
- validParams++;
- } else if (cell.innerHTML.toUpperCase() === 'GL CLASS') {
- colLoc.glClass = i;
- validParams++;
-
- } else if (cell.innerHTML.toUpperCase() === 'SITEID' || cell.innerHTML.toUpperCase() === 'SITE') {
- colLoc.siteID = i;
- validParams++;
- } else if (cell.innerHTML.toUpperCase() === 'STOREROOM' || cell.innerHTML.toUpperCase() === 'STOREROOM') {
- colLoc.storeroom = i;
- validParams++;
- } else if (cell.innerHTML.toUpperCase() === 'VENDOR' || cell.innerHTML.toUpperCase() === 'VENDOR NUMBER') {
- colLoc.vendor = i;
- validParams++;
- } else if (cell.innerHTML.toUpperCase() === 'CAT NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOG NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOGUE NUMBER') {
- colLoc.catNum = i;
- validParams++;
- } else if (cell.innerHTML.toUpperCase() === 'MAXIMO' || cell.innerHTML.toUpperCase() === 'ITEM NUMBER') {
- colLoc.maximo = i;
- validParams++;
- }
- // console.log(validParams)
- }
-
- //Checking if mandatory columns are filled
- if (colLoc.siteID != -1 || colLoc.storeroom != -1 || colLoc.vendor != -1 || colLoc.catNum != -1) {
- if (colLoc.siteID == -1 || colLoc.storeroom == -1) {
- let numMissing = 0;
- let missingCols = "";
- let missingColArr = [];
- console.log("missing params");
- for (const property in colLoc) {
- if (colLoc[property] == -1 && property != "vendor" && property != "catNum") {
- console.log(property);
- numMissing++;
- missingColArr.push(property.toLowerCase());
- }
- }
- missingCols = missingColArr.join(', ');
- document.getElementById("batch-upload-status-text").innerHTML = `Table is missing ${numMissing} column(s): (${missingCols}). Table will not be uploaded!`;
- return;
- }
- }
- else {
- if (validParams < 5) {
- let missingCols = "";
- let missingColArr = [];
- console.log("missing params");
- for (const property in colLoc) {
- if (colLoc[property] == -1 && property != "siteID" && property != "storeroom" && property != "vendor" && property != "catNum") {
- console.log(property);
- missingColArr.push(property.toLowerCase());
- }
- }
- missingCols = missingColArr.join(', ');
- document.getElementById("batch-upload-status-text").innerHTML = `Table is missing ${5 - validParams} column(s): (${missingCols}). Table will not be uploaded!`;
- return;
- }
-
- }
- let invalidItems = 0;
- //Make item for request that includes inventory upload
- if (validParams > 5) {
- let site = undefined;
- let storeroom = undefined;
- let vendor = undefined;
- let catNum = undefined;
- for (let i = 2; i <= rows; i++) {
- let desc = sanitizeString(document.getElementById(i + "-" + colLoc.description).innerHTML);
- let uom = sanitizeString(document.getElementById(i + "-" + colLoc.uom).innerHTML).toUpperCase();
- let commGroup = sanitizeString(document.getElementById(i + "-" + colLoc.commGroup).innerHTML);
- let glclass = sanitizeString(document.getElementById(i + "-" + colLoc.glClass).innerHTML).toUpperCase();
- if (colLoc.siteID != -1) { site = sanitizeString(document.getElementById(i + "-" + colLoc.siteID).innerHTML).toUpperCase(); }
- if (colLoc.storeroom != -1) { storeroom = sanitizeString(document.getElementById(i + "-" + colLoc.storeroom).innerHTML).toUpperCase(); }
- if (colLoc.vendor != -1) { vendor = sanitizeString(document.getElementById(i + "-" + colLoc.vendor).innerHTML); }
- if (colLoc.catNum != -1) { catNum = sanitizeString(document.getElementById(i + "-" + colLoc.catNum).innerHTML); }
- let maximo = sanitizeString(document.getElementById(i + "-" + colLoc.maximo).innerHTML);
- //if all required parameters are not available, don't create the item and move to next row
- if (desc == '' || uom == '' || commGroup == '' || glclass == '' || desc == 0 || uom == 0 || commGroup == 0 || glclass == 0 || site == '' || storeroom == '') {
- updateItemStatus('error', (i - 1));
- items.push('');
- invalidItems++;
- continue;
- }
-
- let item = new Item(undefined, desc, uom, commGroup, glclass, site, storeroom, vendor, catNum);
- if (colLoc.maximo != -1 && maximo != 0 && maximo.toString().length === 7) {
- item.itemnumber = maximo;
- } else if (desc.toUpperCase().includes("DWG")) {
- item.series = 98;
- } else if (commGroup == "490" && glclass == "PLS") {
- //Change when when item num reachs 9920000
- item.series = 991;
- }
- // console.log(item);
- //add the item to the array
- items.push(item);
- }
- }
- //Make item for request that doesn't need inventory upload
- else {
- for (let i = 2; i <= rows; i++) {
- let desc = sanitizeString(document.getElementById(i + "-" + colLoc.description).innerHTML);
- let uom = sanitizeString(document.getElementById(i + "-" + colLoc.uom).innerHTML).toUpperCase();
- let commGroup = sanitizeString(document.getElementById(i + "-" + colLoc.commGroup).innerHTML);
- let glclass = sanitizeString(document.getElementById(i + "-" + colLoc.glClass).innerHTML).toUpperCase();
- let maximo = sanitizeString(document.getElementById(i + "-" + colLoc.maximo).innerHTML);
- //if all required parameters are not available, don't create the item and move to next row
- if (desc == '' || uom == '' || commGroup == '' || glclass == '' || desc == 0 || uom == 0 || commGroup == 0 || glclass == 0) {
- updateItemStatus('error', (i - 1));
- items.push('');
- invalidItems++;
- continue;
- }
- let item = new Item(undefined, desc, uom, commGroup, glclass);
- if (colLoc.maximo != -1 && maximo != 0 && maximo.toString().length === 7) {
- item.itemnumber = maximo;
- } else if (desc.toUpperCase().includes("DWG")) {
- item.series = 98;
- } else if (commGroup == "490" && glclass == "PLS") {
- //Change when when item num reachs 9920000
- item.series = 991;
- }
- // console.log(item);
- //add the item to the array
- items.push(item);
- }
- }
-
- if (invalidItems > 0) {
- document.getElementById("batch-upload-status-text").innerHTML = `Warning! ${invalidItems} invalid items will not be uploaded`;
- }
- //return the item array
- return items;
-}
-
-/**
- * Uploads an item from item information accordion dropdown (single item upload)
- *
- */
-async function uploadItem() {
- document.getElementById("confirm-btn").innerHTML = ' Uploading...';
- document.getElementById("confirm-btn").disabled = true;
- const worker = new WorkerHandler();
- let item = new Item(
- sanitizeString(document.getElementById("interact-num").value),
- sanitizeString(document.getElementById("maximo-desc").value),
- sanitizeString(document.getElementById("uom-field").value),
- sanitizeString(document.getElementById("com-group").value),
- sanitizeString(document.getElementById("gl-class").value)
- );
-
- if (document.getElementById("long-desc").value.length > 0) {
- item.longdescription = document.getElementById("long-desc").value;
- }
-
-
-
- worker.work(['uploadItems', [item]], (e) => {
- console.log(e);
- if(e === undefined || typeof e != 'string' || e == 200) {
- document.getElementById("error").innerHTML = "Upload Success";
- document.getElementById("confirm-btn").innerHTML = "Upload Item";
- document.getElementById("confirm-btn").disabled = false;
- new Toast("Upload Complete!", 'bg-success');
- let itemUrl = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${item.itemnumber}`;
- document.getElementById("error").innerHTML = `Item Upload Successful! (Click to view item) `;
- document.getElementById("item-link").addEventListener('click', function (x) {
- x.preventDefault();
- shell.openExternal(itemUrl);
- });
- } else {
- document.getElementById("error").innerHTML = "Upload Fail";
- document.getElementById("confirm-btn").innerHTML = "Upload Item";
- document.getElementById("confirm-btn").disabled = false;
- //TODO: fail messages
- document.getElementById("error").innerHTML = `Item Upload Failed! ${e}`;
- }
- });
-}
-/**
- * Uploads an array of items
- *
- * @param {Array} items
- */
-async function batchUploadItems(items) {
- const worker = new WorkerHandler();
- //disable clear and upload buttons while uploading items to prevent duplicate requests
- let btn = document.getElementById("batch-upload-btn");
- let clearBtn = document.getElementById("clear-batch-items-btn");
- clearBtn.disabled = true;
- btn.disabled = true;
-
- worker.work(['uploadItems', items, true], (e) => {
- let finishText = `Upload Finished! ${e[2]} items uploaded, ${e[3]} items added to inventory. `;
- if (e[0] == "failure") {
- new Toast(`Invalid! ${e[1]}}!`);
- }
- clearBtn.disabled = false;
- btn.disabled = false;
- updateItemNums(e[0]);
- let rows = parseInt(document.getElementById("batch-items-table").getAttribute("data-rows")) - 1;
- let nums = "";
- for (let i = 2; i <= rows + 1; i++) {
- nums += document.getElementById(`${i}-${colLoc.maximo}`).innerHTML ? (document.getElementById(`${i}-${colLoc.maximo}`).innerHTML + ",") : "";
- }
- if (e[2] > 0) {
- let itemUrl = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${nums}`;
- finishText += `Click to view:`
- document.getElementById("batch-upload-status-text").innerHTML = finishText;
- document.getElementById("batch-link").addEventListener('click', function (e) {
- e.preventDefault();
- shell.openExternal(itemUrl);
- });
- } else {
- document.getElementById("batch-upload-status-text").innerHTML = finishText;
- }
- console.log("upload finished");
- });
-}
-/**
- * Gets a list of newly generated item nums and updates the table with them.
- *
- * If an item has just been uploaded, populates item num cell with new number.
- *
- * @param {int[][]} arr array of pairs of item nums and table row indexes
- */
-function updateItemNums(arr) {
- for (const pair of arr) {
- let itemNum = pair[0];
- let itemRowIndex = pair[1];
-
- //update item number cell
- let cell = document.getElementById(`${itemRowIndex + 1}-${colLoc.maximo}`);
- cell.innerHTML = itemNum;
-
- //highlight the item number yellow to signify that it was newly uploaded
- cell.classList.add("table-alert");
- }
-}
-////////////////////////
-
-function skipRow() {
- let row = document.getElementById("current-row").innerHTML;
- interactiveGoNext(Number(row) + 1);
-}
-
-function finishLoadingBatch(params) {
- let bar = new ProgressBar();
- // this has a special work thread since initializing a worker thread takes ~700 ms which is too long
- document.getElementById("valid-row").innerHTML = params[1];
- document.getElementById("total-row").innerHTML = params[2];
- const worker = new Worker('./worker.js');
- const db = new Database();
- let description = db.getDescription(params[0]);
- if (description === undefined) {
- bar.update(100, 'Done!');
- worker.terminate();
- new Toast('Finished Batch Processing');
- return false;
- }
- bar.update(0, 'Processing Descriptions');
- processBatch(worker, params[0], description);
- worker.onmessage = (msg) => {
- if (msg.data[0] === 'nextrow') {
- description = db.getDescription(msg.data[1]);
- if (description === undefined) {
- params = worksheetParams(document.getElementById("worksheet-path").value);
- worker.postMessage([
- 'saveProcessed',
- [params, msg.data[1]]
- ]);
- new Toast('Finished Batch Processing');
- new Toast('Please wait for file to finish saving...');
- return false;
- }
- document.getElementById("current-row").innerHTML = description.row;
- bar.update(msg.data[1] / params[2] * 100, `Processing Description. Row: ${msg.data[1]} of ${params[2]}`);
- processBatch(worker, msg.data[1], description);
- } else if (msg.data[0] === 'saveComplete') {
- interactiveGoNext(msg.data[1]);
- new Toast('File Saved');
- worker.terminate();
- } else {
- console.log(`IDK: ${msg.data}`);
- }
- };
-}
-
-function processBatch(worker, row, description) {
- const interactive = document.getElementById("modeSelect").checked;
- const related = document.getElementById("relatedSelect").checked;
- const translate = document.getElementById("translateSelect").checked;
- const params = worksheetParams(document.getElementById("worksheet-path").value);
- if (interactive) {
- new Toast('Pausing / Switching to Interactive Mode');
- worker.postMessage([
- 'saveProcessed',
- [params, row]
- ]);
- } else {
- worker.postMessage([
- 'nonInteractive',
- [
- related,
- translate,
- description.description,
- document.getElementById('selected-language').value,
- params,
- row
- ]
- ]);
- }
-
-}
-
-function continueAuto() {
- document.getElementById("modeSelect").checked = false;
- finishLoadingBatch([
- Number(document.getElementById("current-row").innerHTML),
- document.getElementById("valid-row").innerHTML,
- document.getElementById("total-row").innerHTML,
- ]);
-}
-
-function interactiveGoNext(row) {
- let bar = new ProgressBar();
- const db = new Database();
- let description = db.getDescription(row);
- if (description === undefined) {
- bar.update(100, 'Done!');
- new Toast('End of File Reached');
- return false;
- }
- document.getElementById("current-row").innerHTML = description.row;
- if (description) {
- const worker = new WorkerHandler();
- document.getElementById("maximo-desc").value = description.description;
- worker.work(['validSingle', description.description], showResult);
- } else {
- let field = document.getElementById("maximo-desc");
- field.placeholder = "Row is blank, press skip row to go next";
- field.value = "";
- let bar = new ProgressBar();
- bar.update(100, 'Done');
- }
-}
-
-function validSingle(isExtended = false) {
- let bar = new ProgressBar();
- bar.update(0, 'Starting Item Description Validation');
- let raw_desc = document.getElementById("maximo-desc").value;
- const worker = new WorkerHandler();
- worker.work(['validSingle', raw_desc], (result) => {
- showResult(result, isExtended)
- });
-}
-
-function showResult(result, isExtended = false) {
- let triDesc = document.getElementById('result-triple-main');
- triDesc.value = result[0][0];
- triDesc = document.getElementById('result-triple-ext1');
- triDesc.value = result[0][1];
- triDesc = document.getElementById('result-triple-ext2');
- triDesc.value = result[0][2];
- const related = document.getElementById("relatedSelect").checked;
- const translate = document.getElementById("translateSelect").checked;
- calcConfidence(result[0][3]);
- document.getElementById("validate-badge").innerHTML = "New";
- if (translate) {
- translationDescription(result[0][3]);
- }
- if (related) {
- findRelated(result[0], isExtended);
- }
-}
-
-async function ItemAnalysis() {
- const valid = new Validate();
- let raw_desc = document.getElementById("maximo-desc").value;
- let result = await valid.validateSingle(raw_desc);
- let triDesc = document.getElementById('result-triple-main');
- triDesc.value = result[0];
- triDesc = document.getElementById('result-triple-ext1');
- triDesc.value = result[1];
- triDesc = document.getElementById('result-triple-ext2');
- triDesc.value = result[2];
- calcConfidence(result[3]);
-}
-
-function findRelated(result, isExtended = false) {
- const worker = new WorkerHandler();
- worker.work(['findRelated', result[3], isExtended], (result) => { showRelated(result, isExtended) });
-}
-
-function translationDescription(description) {
- // for now do not translate if english is selected
- if (document.getElementById("selected-language").value != 'en') {
- const worker = new WorkerHandler();
- if (document.getElementById('result-triple-ext1').value) {
- description = `${document.getElementById('result-triple-main').value},${document.getElementById('result-triple-ext1').value}`;
- } else {
- description = document.getElementById('result-triple-main').value;
- }
-
- worker.work([
- 'translateItem',
- description,
- document.getElementById('selected-language').value,
- 'post'
- ], displayTranslation);
- } else {
- new Toast('Currently translation into English is not supported');
- }
-
-}
-
-function displayTranslation(data) {
- document.getElementById('trans-desc').value = data[0];
- document.getElementById('translation-description').value = `The following words do not have a translation:\n${data[1]}\nPlease check logs at bottom of page for details`;
- auto_grow('translation-description');
-}
-
-function calcConfidence(data) {
- let description;
- let level = 0;
- let tree = '';
- let parent = 0;
- const regex = /\d+/g;
- const db = new Database();
- let analysis;
- let result = '';
- const option = {
- style: 'percent',
- minimumFractionDigits: 1,
- maximumFractionDigits: 1
- };
- const formatter = new Intl.NumberFormat("en-US", option);
-
- if (data?.length > 0) { //test if description is blank
- description = data.split(',');
- for (let j = 0; j < description.length; j++) {
- if (!(description[j].match(regex))) {
- if (db.isManufacturer(description[j])) {
- result = `${result}\n${description[j]} is confirmed as a manufacturer`;
- } else {
- level++;
- if (level == 1) {
- tree = description[j];
- } else {
- tree = tree + ',' + description[j];
- }
- analysis = db.getAnalysis(tree);
- if (analysis) {
- if (level == 1) {
- if (analysis.count >= 100) {
- result = `${description[j]}: is COMMONLY used as an Item Type.\n${analysis.count}: occurrences found`;
- } else if (analysis.count >= 20) {
- result = `${description[j]}: is SOMETIMES used as an Item Type.\n${analysis.count}: occurrences found`;
- } else {
- result = `WARNING: ${description[j]}: is an UNCOMMON Item Type.\nPlease double check.\n${analysis.count}: occurrences found`;
- }
- } else {
- if (analysis.count / parent >= 0.25) {
- result = `${result}\n${description[j]} is COMMONLY used as an item descriptor for ${tree}.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
- } else if (analysis.count / parent >= 0.05) {
- result = `${result}\n${description[j]} is SOMETIMES used as an item descriptor for ${tree}.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
- } else {
- result = `${result}\n${description[j]} is an UNCOMMON item descriptor for ${tree}.\nPlease double check.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
- }
- }
- parent = analysis.count;
- } else {
- result = `${result}\n${description[j]}: Does not exist in Maximo as part of: ${tree}.\nPlease Check with Corporate`;
- }
- }
- }
- }
- document.getElementById('valid-description').value = result.trim();
- } else {
- new Toast('Blank Description');
- }
-}
-
-/**
- * Initializes search results table and populates relatedResults object
- * with search results.
- *
- * @param {Array< Map>,Map,String>} result array of [array of item nums of all search results, map of item nums to descriptions, and search query with words separated by commas]
- * @param {bool} isExtended whether the user has clicked extended search
- */
-async function showRelated(result, isExtended = false) {
- let bar = new ProgressBar();
- if (!result[0]) {
- bar.update(100, 'Done!');
- return false;
- }
-
- //reverse results to get newest items first.
- //technically this isn't the best way to do
- //this because results aren't guaranteed
- //to be in order of oldest to newest.
- for (const [key, value] of Object.entries(result[0])) {
- result[0][key] = result[0][key].reverse();
- }
- //populate global variable with search results (bad practice, but it works)
- relatedResults = {
- idx: 0,
- curKey: 0,
- results: result,
- }
-
- //reset table after called
- const relatedTable = document.getElementById('related-table');
- const numResultsText = document.getElementById('num-results');
-
- if (isExtended) {
- relatedTable.classList.add(`isExt`);
- } else {
- if (relatedTable.classList.contains(`isExt`)) {
- relatedTable.classList.remove(`isExt`);
- }
- }
- //Add headings to the search results table
- relatedTable.innerHTML = `
-
-
-
-
Percent Match
-
Item Number
-
Item Description
- ${(isExtended ? '
More Info
' : '')}
-
UOM
-
C_Group
-
GL_Class
-
-
-
-
-
- `;
-
- numResultsText.innerHTML = `Found ${Object.entries(result[1]).length} results`;
-
- //expand the search results accordion
- document.getElementById('related-items-accordion-btn').classList.remove('collapsed');
- //load a couple of items
- loadRelated();
- html = new bootstrap.Collapse(document.getElementById('accordion-relatedItem'), { toggle: false });
- html.show();
- bar.update(100, 'Done!');
-}
-
-function loadRelated() {
- //check if user clicked extended search
- const isExtended = document.getElementById('related-table').classList.contains('isExt');
-
- //a map with percent match as key (in decimal form) and array of items as value
- //for example, if the key is 1, the list of items match with the search query 100%. If the key is 0.5, the list of items match with the search query 50%.
- const scores = relatedResults.results[0];
-
- //relatedResults.idx is like a bookmark. It keeps track of how many items have been loaded from the array of items associated with the current key in scores.
- //relatedResults.curKey is the number of the current key that is being loaded if you were to iterate thru the keys in scores.
- //For example, the first key's number would be 0, second key 1, etc.
- if (relatedResults.curKey >= Object.entries(scores).length) {
- //If curKey is equal or larger than the number of keys in scores, then there are no more items to load, so return
- return;
- } else if (Object.entries(scores)[relatedResults.curKey][1].length == 0) {
- //If there are no items associated with the current key, then move to the next key and try loading items again.
- relatedResults.curKey++; //increment curKey so that the next time the function runs, it will try to load items from the next key
- relatedResults.idx = 0; //reset idx so that it starts from the beginning of the array
- loadRelated();
- return; //return so we don't do make an infinite loop
- }
-
- let step = 20; //number of items to load at once.
-
- //get arrs from results obj
- const itemNames = relatedResults.results[1]; //a map with the 9-series number of the item as the key and the item info as value. Item info is an array with 4 items: [description, gl class, uom, commodity group]
- const searchWords = relatedResults.results[2].split(','); //an array of the search query split by commas. For example, if the search query is "test, item, description", then searchWords would be ["test", "item", "description"]
-
- let html = '', color = ''; //html is the html that will be added to the search results table. color is the color of the row in the table.
-
- let itemDescription;
-
- //formatting options for percent match. Converts decimal to percent and rounds to nearest whole number.
- const option = {
- style: 'percent',
- minimumFractionDigits: 0,
- maximumFractionDigits: 0
- };
-
- const formatter = new Intl.NumberFormat("en-US", option);
- // technically this is bad practise since object order might not be guarenteed
- // https://stackoverflow.com/questions/983267/how-to-access-the-first-property-of-a-javascript-object
-
- let percentMatch = Object.entries(scores)[relatedResults.curKey][0]; //get the percent match (name of the current key)
- let itemNumList = Object.entries(scores)[relatedResults.curKey][1]; //get the array of items associated with key
- let itemsToLoad; //array of items to load
-
- if (relatedResults.idx + step >= itemNumList.length) {
- //if there are less than 20 items to load, load the remaining items in value and increment curKey and reset idx
- //this way, the next time the function is called, the next key will be loaded instead
- itemsToLoad = itemNumList.slice(relatedResults.idx, undefined); //get array of items from idx to end of array
- relatedResults.curKey++;
- relatedResults.idx = 0;
- } else {
- itemsToLoad = itemNumList.slice(relatedResults.idx, relatedResults.idx + step);
- relatedResults.idx += step;
- }
-
- // iterate thru each item in value array
- for (let itemNum of itemsToLoad) {
- itemDescription = itemNames[itemNum][0];
- if (itemDescription) {
- //Bold all words in item description that match the search query
- for (let word of searchWords) {
- split = word.split(' ');
- for (let smallWord of split) {
- if (smallWord.length > 0) {
- itemDescription = itemDescription.replace(
- new RegExp(`${smallWord}`, 'i'),
- `${itemDescription.match(new RegExp(`${smallWord}`, 'i'))?.[0]}`
- );
- }
- }
-
- }
- //set row color based on percent match
- if (percentMatch > 0.7) {
- color = 'table-success'; //green
- } else if (percentMatch > 0.4) {
- color = 'table-warning'; //yellow
- } else {
- color = 'table-danger'; //red
- }
-
- //create HTML row.
- //In extended search, the vendor info is split from the item description by a | (pipe character).
- //All info after the pipe character is put into another column.
- //If the item description does not have a pipe character, then the second column is not loaded.
- html = `${html}\n
`;
- }
- }
-
- //add html to table
- const relatedTable = document.getElementById('related-items');
- relatedTable.innerHTML += html;
-
- //if less than 5 items loaded, load more
- if (itemsToLoad.length < 5) {
- document.getElementById("everything").dispatchEvent(new Event('scroll'));
- }
-}
-
-//unused function (was used to copy item validation): probably remove this
-function copyResult(copy) {
- if (copy === 'single') {
- let content = document.getElementById('result-single').innerText;
- clipboard.writeText(content);
- new Toast('Single Description Copied to Clipboard!');
- } else {
- let desc = [];
- let content = '';
- content = document.getElementById('result-triple-main').innerText;
- desc.push(content);
- content = document.getElementById('result-triple-ext1').innerText;
- desc.push(content);
- content = document.getElementById('result-triple-ext2').innerText;
- desc.push(content);
- clipboard.write({
- text: document.getElementById('result-single').innerText,
- html: `
${desc[0]}
${desc[1]}
${desc[2]}
`,
- });
- new Toast('Triple Description Copied to Clipboard!');
- }
-}
diff --git a/assets/asset_translation/asset_translation_excel.js b/src/asset_translation/asset_translation_excel.js
similarity index 100%
rename from assets/asset_translation/asset_translation_excel.js
rename to src/asset_translation/asset_translation_excel.js
diff --git a/assets/asset_translation/asset_translation_main.js b/src/asset_translation/asset_translation_main.js
similarity index 100%
rename from assets/asset_translation/asset_translation_main.js
rename to src/asset_translation/asset_translation_main.js
diff --git a/assets/asset_translation/asset_translation_sqlite.js b/src/asset_translation/asset_translation_sqlite.js
similarity index 100%
rename from assets/asset_translation/asset_translation_sqlite.js
rename to src/asset_translation/asset_translation_sqlite.js
diff --git a/assets/item_translation/item-translation-sqlite.js b/src/item_translation/item-translation-sqlite.js
similarity index 100%
rename from assets/item_translation/item-translation-sqlite.js
rename to src/item_translation/item-translation-sqlite.js
diff --git a/assets/item_translation/item-translation.js b/src/item_translation/item-translation.js
similarity index 99%
rename from assets/item_translation/item-translation.js
rename to src/item_translation/item-translation.js
index faef978..c585c92 100644
--- a/assets/item_translation/item-translation.js
+++ b/src/item_translation/item-translation.js
@@ -1,5 +1,5 @@
const TransDB = require('./item-translation-sqlite');
-const Database = require('../indexDB');
+const Database = require('../misc/indexDB');
class TranslateDescription {
contextTranslate(description, lang_code, result) {
diff --git a/main.js b/src/main.ts
similarity index 72%
rename from main.js
rename to src/main.ts
index 40b129c..94ff3a3 100644
--- a/main.js
+++ b/src/main.ts
@@ -1,11 +1,11 @@
// Modules to control application life and create native browser window
-const {app, BrowserWindow, ipcMain, screen, dialog, shell} = require('electron');
-const path = require('path');
-const fs = require('fs');
-const {appUpdater} = require('./assets/autoupdater');
-const CONSTANTS = require('./assets/constants.js');
-require('electron-reload')(__dirname)
-let mainWindow;
+import {app, BrowserWindow, ipcMain, screen, dialog, shell} from 'electron';
+import path from 'path';
+import fs from 'fs';
+import { appUpdater } from './misc/autoupdater.js';
+import CONSTANTS from './misc/constants.js';
+require('electron-reload')(__dirname);
+let mainWindow: Electron.BrowserWindow;
let settingWindow;
if (require('electron-squirrel-startup')) {
@@ -14,27 +14,27 @@ if (require('electron-squirrel-startup')) {
// Write eml file
ipcMain.on('write-file', (event, emailData) => {
const pathToFile = path.resolve(__dirname, 'downloadedFile.eml');
- fs.writeFile(pathToFile, emailData, (err) => {
+ fs.writeFile(pathToFile, emailData, (err: any) => {
if (err) {
console.error(`Error writing file: ${err}`);
} else {
shell.openPath(pathToFile)
- .then(() => {
- sleep(2000).then(() => {
- // Delete the file after opening
- fs.unlink(pathToFile, (err) => {
- if (err) {
- console.error(`Error deleting file: ${err}`);
- } else {
- console.log('File deleted successfully');
- }
- });
- },
- )
- .catch((err) => {
- console.error(`Error opening file: ${err}`);
- });
- });
+ // .then(() => {
+ // sleep(2000).then(() => {
+ // // Delete the file after opening
+ // fs.unlink(pathToFile, (err: any) => {
+ // if (err) {
+ // console.error(`Error deleting file: ${err}`);
+ // } else {
+ // console.log('File deleted successfully');
+ // }
+ // });
+ // },
+ // )
+ // .catch((err: any) => {
+ // console.error(`Error opening file: ${err}`);
+ // });
+ // });
}
});
});
@@ -52,7 +52,7 @@ ipcMain.on('openSettings', (event, arg) => {
},
});
- settingWindow.loadFile(path.join('renderer', 'setting.html'));
+ settingWindow.loadFile(path.join('src', 'renderer', 'setting.html'));
settingWindow.show();
settingWindow.on('closed', () => {
mainWindow.show();
@@ -111,23 +111,23 @@ ipcMain.on('getPath', (event, arg) => {
});
ipcMain.on('loading', (event, arg) => {
- mainWindow.loadFile(path.join('renderer', 'item_main.html'));
+ mainWindow.loadFile(path.join('src', 'renderer', 'item_main.html'));
});
ipcMain.on('start_item_module', (event, arg) => {
- mainWindow.loadFile(path.join('renderer', 'item_loading.html'));
+ mainWindow.loadFile(path.join('src', 'renderer', 'item_loading.html'));
});
ipcMain.on('start_observation_template', (event, arg) => {
- mainWindow.loadFile(path.join('renderer', 'observation_template.html'));
+ mainWindow.loadFile(path.join('src', 'renderer', 'observation_template.html'));
});
ipcMain.on('start_item_translate', (event, arg) => {
- mainWindow.loadFile(path.join('renderer', 'item_translation.html'));
+ mainWindow.loadFile(path.join('src', 'renderer', 'item_translation.html'));
});
ipcMain.on('start_asset_translate', (event, arg) => {
- mainWindow.loadFile(path.join('renderer', 'asset_translation.html'));
+ mainWindow.loadFile(path.join('src', 'renderer', 'asset_translation.html'));
});
function createWindow() {
@@ -147,7 +147,7 @@ function createWindow() {
});
// and load the index.html of the app.
- mainWindow.loadFile(path.join('renderer', 'start_page.html'));
+ mainWindow.loadFile(path.join('src', 'renderer', 'start_page.html'));
// Open the DevTools.
if(CONSTANTS.OPEN_DEV_TOOLS) {
diff --git a/src/misc/autoupdater.ts b/src/misc/autoupdater.ts
new file mode 100644
index 0000000..aebe937
--- /dev/null
+++ b/src/misc/autoupdater.ts
@@ -0,0 +1,43 @@
+import os from 'os'
+import { app, autoUpdater, dialog, type FeedURLOptions } from 'electron'
+const version = app.getVersion()
+const platform = os.platform() + '_' + os.arch() // usually returns darwin_64
+
+const updaterFeedURL = 'https://jonathanmajh-iko-mro-items.onrender.com/update/' + platform + '/' + version
+// replace updaterFeedURL with https://l3gxze.deta.dev
+const urlOptions: FeedURLOptions = { url: updaterFeedURL }
+
+export function appUpdater (): void {
+ autoUpdater.setFeedURL(urlOptions)
+ /* Log whats happening
+TODO send autoUpdater events to renderer so that we could console log it in developer tools
+You could alsoe use nslog or other logging to see what's happening */
+ autoUpdater.on('error', err => { console.log(err) })
+ autoUpdater.on('checking-for-update', () => { console.log('checking-for-update') })
+ autoUpdater.on('update-available', () => {
+ console.log('update-available')
+ })
+ autoUpdater.on('update-not-available', () => { console.log('update-not-available') })
+
+ // Ask the user if update is available
+ autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
+ console.log('update-downloaded')
+ // Ask user to update the app
+ const selected = dialog.showMessageBoxSync({
+ type: 'question',
+ buttons: ['Update and Relaunch', 'Later'],
+ defaultId: 0,
+ message: 'Update Available!',
+ detail: `A new version of ${app.getName()} has been downloaded\nDo you want to update now?\nUpdate will be automatically installed on next start up.`
+ })
+ if (selected === 0) {
+ autoUpdater.quitAndInstall()
+ }
+ })
+ // init for updates
+ autoUpdater.checkForUpdates()
+}
+
+exports = module.exports = {
+ appUpdater
+}
diff --git a/assets/better-sqlite.js b/src/misc/better-sqlite.js
similarity index 100%
rename from assets/better-sqlite.js
rename to src/misc/better-sqlite.js
diff --git a/assets/common.js b/src/misc/common.js
similarity index 100%
rename from assets/common.js
rename to src/misc/common.js
diff --git a/assets/constants.js b/src/misc/constants.js
similarity index 100%
rename from assets/constants.js
rename to src/misc/constants.js
diff --git a/assets/exceljs.js b/src/misc/exceljs.js
similarity index 100%
rename from assets/exceljs.js
rename to src/misc/exceljs.js
diff --git a/assets/indexDB.js b/src/misc/indexDB.js
similarity index 99%
rename from assets/indexDB.js
rename to src/misc/indexDB.js
index 4fb8251..b6ae8ce 100644
--- a/assets/indexDB.js
+++ b/src/misc/indexDB.js
@@ -1,5 +1,5 @@
const Sql = require('better-sqlite3');
-const utils = require('../assets/utils');
+const utils = require('./utils');
const intersection = require('lodash/intersection');
// https://lodash.com/docs/4.17.15#intersection
// fast library for intersection of arrays
diff --git a/assets/maximo.js b/src/misc/maximo.js
similarity index 99%
rename from assets/maximo.js
rename to src/misc/maximo.js
index 56d1234..21152cb 100644
--- a/assets/maximo.js
+++ b/src/misc/maximo.js
@@ -1,6 +1,6 @@
// various functions for fetching data from maximo rest api
-const SharedDatabase = require('../assets/sharedDB');
-const CONSTANTS = require('../assets/constants.js');
+const SharedDatabase = require('./sharedDB');
+const CONSTANTS = require('./constants.js');
/**
@@ -313,11 +313,9 @@ class Maximo {
});
const content = await response.json();
const statuscode = response.status;
- if(statuscode == 200) {
-
+ if (statuscode == 200) {
return parseInt(content.validdoc);
- }
- else {
+ } else {
throw new Error(parseInt(statuscode));
}
}
diff --git a/assets/sharedDB.js b/src/misc/sharedDB.js
similarity index 100%
rename from assets/sharedDB.js
rename to src/misc/sharedDB.js
diff --git a/assets/spreadsheet.js b/src/misc/spreadsheet.js
similarity index 100%
rename from assets/spreadsheet.js
rename to src/misc/spreadsheet.js
diff --git a/assets/utils.js b/src/misc/utils.js
similarity index 100%
rename from assets/utils.js
rename to src/misc/utils.js
diff --git a/assets/validators.js b/src/misc/validators.js
similarity index 100%
rename from assets/validators.js
rename to src/misc/validators.js
diff --git a/renderer/asset_translation.html b/src/renderer/asset_translation.html
similarity index 98%
rename from renderer/asset_translation.html
rename to src/renderer/asset_translation.html
index eddab36..1ca4635 100644
--- a/renderer/asset_translation.html
+++ b/src/renderer/asset_translation.html
@@ -7,7 +7,7 @@
EAM Spare Parts
-
+
diff --git a/renderer/asset_translation.js b/src/renderer/asset_translation.js
similarity index 100%
rename from renderer/asset_translation.js
rename to src/renderer/asset_translation.js
diff --git a/renderer/item_main.html b/src/renderer/item_main.html
similarity index 99%
rename from renderer/item_main.html
rename to src/renderer/item_main.html
index 8a9b061..1df23de 100644
--- a/renderer/item_main.html
+++ b/src/renderer/item_main.html
@@ -7,11 +7,11 @@
-
+
EAM Spare Parts
-
-
+
+
@@ -352,7 +352,7 @@
Confirm Upload
-
+
Item Standardizer
diff --git a/src/renderer/item_main.js b/src/renderer/item_main.js
new file mode 100644
index 0000000..321bb19
--- /dev/null
+++ b/src/renderer/item_main.js
@@ -0,0 +1,1421 @@
+const {clipboard, ipcRenderer, shell} = require('electron');
+const fs = require('fs');
+const path = require('path');
+// const { dialog } = require('electron').remote;
+const Database = require('../misc/indexDB');
+const SharedDatabase = require('../misc/sharedDB');
+const Validate = require('../misc/validators');
+const Maximo = require('../misc/maximo');
+const CONSTANTS = require('../misc/constants.js');
+// stores items that are to be uploaded through the "batch upload" accordion.
+let itemsToUpload = [];
+
+// stores images that are to be uploaded through the "image upload" accordion.
+let imgsToUpload = [];
+
+// an object that stores the location of each column in the batch upload table.
+// allows for column locations to be interchanged. -1 means a column is not in
+// the table. Maybe in the future, column locations should be predetermined so
+// that a global variable is not used for this.
+let colLoc = {
+ description: -1,
+ uom: -1,
+ commGroup: -1,
+ glClass: -1,
+ maximo: -1,
+ vendor: -1,
+ storeroom: -1,
+ catNum: -1,
+ siteID: -1,
+};
+
+// an object that stores all search results and "bookmarks" how many items have
+// been loaded in the related results table. Used for infinite scroll.
+let relatedResults = {
+ idx: 0, // store the index of the current item being loaded
+ curKey: 0, // store which key is currently being loaded from the search results
+ results: [], // store all search results (dictonary)
+};
+
+// a function that is called immediately after the window has been loaded
+window.onload = function() {
+ // set the darkmode toggle to the correct position by retreiving information from the local storage
+ document.getElementById('dark-mode-switch').checked = (localStorage.getItem('theme') === 'dark' ? true : false);
+
+ // change the UI based on whether the user is a "power user". show all upload elements if they are a power user, else hide it.
+ if (localStorage.getItem('powerUser') === 'true') {
+ document.getElementById('upload-btn').style.display = 'block';
+ document.getElementById('request-btn').style.display = 'none';
+ document.getElementById('batch-upld-btn').style.display = 'block';
+ document.getElementById('img-upld-toggle').style.display = 'block';
+ document.getElementById('batch-mode-toggle').style.display = 'block';
+ return;
+ }
+};
+
+/*
+Power User Toggle
+
+Allows user to toggle between 2 modes: Power user and Normal User.
+Normal user mode hides all upload elements and only allows the user to request items.
+Power user mode shows all upload elements and allows the user to upload items and images.
+
+Created for the purpose of hiding upload functionality from people who shouldn't be
+uploading items (a.k.a. everyone except for reliability team).
+*/
+// set the user to a power user if they have clicked the secret button 5 times
+document.getElementById('secret-button').addEventListener('click', (e) => {
+ let isPowerUser = false;
+ let numClicks = parseInt(e.target.getAttribute('data-clicks'));
+
+ numClicks++;
+
+ if (numClicks === 5) {
+ isPowerUser = true;
+ localStorage.setItem('powerUser', 'true');
+ e.target.setAttribute('data-clicks', '0');
+ } else {
+ localStorage.setItem('powerUser', 'false');
+ e.target.setAttribute('data-clicks', `${numClicks}`);
+ isPowerUser = false;
+ }
+
+ // toggle whether elements are hidden or not based off of power user status
+ if (isPowerUser == true) {
+ document.getElementById('upload-btn').style.display = 'block';
+ document.getElementById('request-btn').style.display = 'none';
+ document.getElementById('batch-upld-btn').style.display = 'block';
+ document.getElementById('img-upld-toggle').style.display = 'block';
+ document.getElementById('batch-mode-toggle').style.display = 'block';
+ } else {
+ document.getElementById('upload-btn').style.display = 'none';
+ document.getElementById('request-btn').style.display = 'block';
+ document.getElementById('batch-upld-btn').style.display = 'none';
+ document.getElementById('img-upld-toggle').style.display = 'none';
+ document.getElementById('batch-mode-toggle').style.display = 'none';
+ }
+});
+
+// gets user site information
+async function getSite(credentials = {}) {
+ const maximo = new Maximo();
+ const currInfo = await maximo.checkLogin(credentials?.userid, credentials?.password);
+ return currInfo.siteID;
+}
+
+// open a modal that allows you to make an item request
+document.getElementById('request-btn').addEventListener('click', () => {
+ // show request item modal
+ const requestModal = new bootstrap.Modal(document.getElementById('requestModal'));
+ requestModal.toggle();
+
+ const currPass = new SharedDatabase().getPassword();
+ const userid = currPass.userid;
+ let siteID;
+
+ const sites = {
+ 'AA': ['AAG: Brampton B2 Storeroom', 'AAL: Brampton B2/B4 Maintenance Storeroom', 'AAO: Brampton B4 Oxidizer Storeroom'],
+ 'ANT': ['AN1: Antwerp Mod Line Storeroom', 'AN2: Antwerp Coating Line Storeroom'],
+ 'BA': ['BAL: IKO Calgary Maintenance Storeroom'],
+ 'BL': ['BLC: Hagerstown TPO Storeroom', 'BLD: Hagerstown ISO Storeroom', 'BLL: Hagerstown Maintenance Storeroom(Shared)'],
+ 'CA': ['CAL">IKO Kankakee Maintenance Storeroom'],
+ 'CAM': ['C61">IKO Appley Bridge Maintenance Storeroom'],
+ 'COM': ['CB1">Combronde Maintenance Storeroom'],
+ 'GA': ['GAL: IKO Wilmington Maintenance Storeroom'],
+ 'GC': ['GCL: Sumas Maintenance Storeroom', 'GCA: Sumas Shipping Storeroom', 'GCD: Sumas Shingle Storeroom', 'GCG: Sumas Mod Line Storeroom', 'GCJ: Sumas Crusher Storeroom', 'GCK: Sumas Tank Farm Storeroom'],
+ 'GE': ['GEL: Ashcroft Maintenance Storeroom'],
+ 'GH': ['GHL: IKO Hawkesbury Maintenance Storeroom'],
+ 'GI': ['GIL: IKO Madoc Maintenance Storeroom'],
+ 'GJ': ['GJL: CRC Toronto Maintenance Storeroom'],
+ 'GK': ['GKA: IG Brampton B7 and B8 Storeroom', 'GKC: IG Brampton B6 and Laminator Storeroom', 'GKL: IG Brampton Maintenance Storeroom'],
+ 'GM': ['GML: IG High River Maintenance Storeroom'],
+ 'GP': ['GPL: CRC Brampton Maintenance Storeroom'],
+ 'GR': ['GRL: Bramcal Maintenance Storeroom'],
+ 'GS': ['GSL: Sylacauga Maintenance Storeroom'],
+ 'GV': ['GVL: IKO Hillsboro Maintenance Storeroom'],
+ 'GX': ['GXL: Maxi-Mix Maintenance Storeroom'],
+ 'KLU': ['KD1: IKO Klundert Maintenance Storeroom', 'KD2: IKO Klundert Lab Storeroom', 'KD3: IKO Klundert Logistics Storeroom'],
+ 'PBM': ['PB6: Slovakia Maintenance Storeroom'],
+ 'RAM': ['RA6: IKO Alconbury Maintenance Storeroom'],
+ // Add more sites and storerooms as needed...
+ };
+
+ const userSite = getSite({userid: userid, password: currPass.password});
+ userSite.then((response) => {
+ siteID = response;
+
+ const storeroomSelect = document.getElementById('storeroom');
+ // poppulate correct user storerooms in modal
+ function updateStoreroomOptions() {
+ storeroomSelect.options.length = 1;
+
+ // Add new options
+ const neededStorerooms = sites[siteID];
+ for (const storeroom of neededStorerooms) {
+ const option = document.createElement('option');
+ option.value = storeroom;
+ option.text = storeroom;
+ storeroomSelect.add(option);
+ }
+ }
+ updateStoreroomOptions();
+ })
+ .catch((error) => console.error(`Error: ${error}`));
+
+ poppulateModal();
+});
+
+// Allow input of manufacturer name & part number if "Other" is selected
+document.getElementById('manu-name').addEventListener('click', (e) => {
+ if (e.target.value == 'Other') {
+ document.getElementById('pref-manu').style.display = 'block';
+ document.getElementById('part-form').style.display = 'block';
+ } else {
+ document.getElementById('pref-manu').style.display = 'none';
+ document.getElementById('part-form').style.display = 'none';
+ }
+});
+
+// download email file when submit button is pressed
+document.getElementById('submit-btn').addEventListener('click', submitMail, false);
+
+// opens email file in default mail client
+function submitMail() {
+ // checking required fields are filled
+ if (document.getElementById('manu-name').value == 'Other') {
+ if (!(document.getElementById('part-num').reportValidity() &&
+ document.getElementById('storeroom').reportValidity() &&
+ document.getElementById('item-descr').reportValidity())) {
+ console.log('Required fields still empty');
+ return;
+ }
+ } else {
+ if (!(document.getElementById('storeroom').reportValidity() &&
+ document.getElementById('item-descr').reportValidity())) {
+ console.log('Required fields still empty');
+ return;
+ }
+ }
+ // storing current date and time for email subject
+ const currentdate = new Date();
+ const datetime = currentdate.getFullYear() + '/' + (currentdate.getMonth() + 1) + '/' + (currentdate.getDay() + 1) +
+ ' @ ' +
+ currentdate.getHours() + ':' +
+ currentdate.getMinutes() + ':' + currentdate.getSeconds();
+ const mailText =
+ ``;
+
+ // Send string to main process to write file
+ ipcRenderer.send('write-file', mailText);
+ // requestModal.toggle();
+}
+
+/* Infinite scroll
+
+Allows elements to load as the user scrolls down the page,
+drastically decreasing loading times and making UI smoother.
+*/
+// listen for a scroll event. if the bottom of the results table is less than 100px below the bottom of the viewport, load more items
+document.getElementById('everything').addEventListener('scroll', () => {
+ // dont add items to the list if the accordion is collapsed
+ if (document.getElementById('related-items-accordion-btn').classList.contains('collapsed') || relatedResults.results.length == 0) {
+ return;
+ }
+
+ const searchResultsTable = document.getElementById('related-items');
+
+ const domRect = searchResultsTable.getBoundingClientRect();
+ const spaceBelow = document.getElementById('everything').offsetHeight - domRect.bottom;
+
+ if (spaceBelow > -100) {
+ // load more items if the bottom of the table is less than 100px below the bottom of the viewport
+ loadRelated();
+ }
+});
+
+// Generate UI when files are selected by user
+document.getElementById('imgInput').addEventListener('change', async (e) => {
+ const progressBar = new ProgressBar();
+
+ // reset UI
+ document.getElementById('imgList').innerHTML = ``;
+ // get files from file picker
+ const files = document.getElementById('imgInput').files;
+ imgsToUpload = files;
+ // make a comma separated string of all the item numbers that are to be uploaded
+ let nums = '';
+
+ // if no files were selected, return
+ if (files.length == 0 || !files) {
+ return;
+ }
+
+ const imgList = document.getElementById('imgList');
+
+ progressBar.update(0, 'Loading Images...');
+
+ // for each image, add list item to HTML list
+ for (let i = 0; i < files.length; i++) {
+ const file = files[i];
+ const completion = (i + 1) / files.length * 100;
+
+ nums += file.name.slice(0, 7) + ','; // get first 7 characters of file name
+ progressBar.updateProgressBar(completion);
+
+ imgList.innerHTML += `
+
+
+
+
${file.name.slice(0, 7)}
+
+ pending
+
`;
+ }
+
+ // generate a link to open items that are being uploaded to in maximo
+
+ const url = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${nums}`;
+ document.getElementById('img-upload-status-text').innerHTML = `Selected Items:`;
+ document.getElementById('imgs-link').addEventListener('click', function(e) {
+ e.preventDefault();
+ shell.openExternal(url);
+ });
+
+ progressBar.update(100, 'Ready to Upload!');
+});
+
+// clear the file picker each time it is clicked
+document.getElementById('imgInput').addEventListener('click', () => {
+ document.getElementById('img-clear-btn').dispatchEvent(new Event('click'));
+});
+
+document.getElementById('img-clear-btn').addEventListener('click', () => {
+ // reset all related components
+ const progressBar = new ProgressBar();
+ document.getElementById('imgList').innerHTML = ``;
+ progressBar.update(100, 'Ready!');
+ // empty list of images to upload
+ imgsToUpload = [];
+ // empty file picker
+ document.getElementById('imgInput').value = null;
+
+ document.getElementById('img-upload-status-text').innerHTML = 'Select Images to Continue...';
+});
+document.getElementById('img-upload-btn').addEventListener('click', () => {
+ const progressBar = new ProgressBar();
+
+ const clearBtn = document.getElementById('img-clear-btn');
+ const uploadBtn = document.getElementById('img-upload-btn');
+
+ // return if user has picked no images
+ if (imgsToUpload.length == 0) {
+ new Toast('No Images Selected!');
+ return;
+ }
+
+ let finishedItems = 0;
+
+ // disable the clear and upload buttons while upload is taking place so the
+ // user can't send duplicate requests or accidentally clear the image upload list
+ // while its uploading
+ clearBtn.disabled = true;
+ uploadBtn.disabled = true;
+
+ const worker = new WorkerHandler();
+
+ progressBar.update(0, 'Uploading Images...');
+
+ // upload all images and update UI
+ worker.work(['uploadImages', imgsToUpload], (result) => {
+ if (result[0] == 'success') {
+ // if success, display checkmark
+ document.getElementById(`img-${result[1]}-status`).innerHTML = `done`;
+ } else if (result[0] == 'fail') {
+ // if fail, display 'x' (cross)
+ document.getElementById(`img-${result[1]}-status`).innerHTML = `close`;
+ } else if (result[0] == 'done') {
+ progressBar.update(100, 'Upload Complete!');
+ clearBtn.disabled = false;
+ uploadBtn.disabled = false;
+ } else if (result[0] == 'warning') {
+ // if warning, display triangle with exclamation point in it. This only occurs if you try
+ // to upload an image to an item that already has an image
+ document.getElementById(`img-${result[1]}-status`).innerHTML = `warning`;
+ } else if (result[0] == 'total failure') {
+ finishedItems = imgsToUpload.length;
+ progressBar.update(100, 'Error occurred while attempting upload!');
+ document.getElementById('img-upload-status-text').innerHTML = `Upload Failed: ${result[1]}}`;
+ clearBtn.disabled = false;
+ uploadBtn.disabled = false;
+ }
+
+ if (result != 'done') {
+ finishedItems++;
+ }
+
+ // update progressbar when each image is uploaded/fails upload
+ progressBar.updateProgressBar(finishedItems * 100 / imgsToUpload.length);
+ });
+});
+
+// Other
+document.getElementById('load-item').addEventListener('click', loadItem);
+document.getElementById('valid-single').addEventListener('click', () => {
+ validSingle();
+});
+document.getElementById('valid-single-ext').addEventListener('click', () => {
+ validSingle(true);
+});
+document.getElementById('settings').addEventListener('click', openSettings);
+document.getElementById('topButton').addEventListener('click', toTop);
+document.getElementById('endButton').addEventListener('click', toEnd);
+document.getElementById('interactive').addEventListener('click', openExcel);
+document.getElementById('worksheet-path').addEventListener('click', openExcel);
+document.getElementById('pauseAuto').addEventListener('click', pauseAuto);
+
+document.getElementById('save-desc').addEventListener('click', writeDescription);
+document.getElementById('save-num').addEventListener('click', writeItemNum);
+document.getElementById('skip-row').addEventListener('click', skipRow);
+document.getElementById('continueAuto').addEventListener('click', continueAuto);
+document.getElementById('confirm-btn').addEventListener('click', () => {
+ uploadItem();
+});
+document.getElementById('upload-btn').addEventListener('click', () => {
+ const confirmModal = new bootstrap.Modal(document.getElementById('confirmModal'));
+ if (!(
+ document.getElementById('maximo-desc').reportValidity() &&
+ document.getElementById('uom-field').reportValidity() &&
+ document.getElementById('com-group').reportValidity() &&
+ document.getElementById('gl-class').reportValidity()
+ )) {
+ return;
+ }
+ ItemAnalysis();
+ confirmModal.toggle();
+ getNextNumThenUpdate(document.getElementById('num-type').value);
+});
+
+
+// batch upload:
+document.getElementById('openBatchFile').addEventListener('click', () => {
+ openFile('worksheet-path');
+});
+
+document.getElementById('clear-batch-items-btn').addEventListener('click', () => {
+ document.getElementById('batch-items-table').innerHTML = ``;
+ document.getElementById('batch-copy-nums').disabled = true;
+ document.getElementById('batch-upload-status-text').innerHTML = 'Waiting for paste...';
+});
+
+document.getElementById('batch-copy-nums').addEventListener('click', () => {
+ try {
+ const result = getItemsFromTable('batch-items-table');
+ if (result == undefined || result == null || result == 0) {
+ throw ('Table missing columns');
+ }
+ const rows = parseInt(document.getElementById('batch-items-table').getAttribute('data-rows')) - 1;
+ let nums = '';
+ for (let i = 2; i <= rows + 1; i++) {
+ nums += document.getElementById(`${i}-${colLoc.maximo}`).innerHTML ? (document.getElementById(`${i}-${colLoc.maximo}`).innerHTML + '\n') : '';
+ }
+ navigator.clipboard.writeText(nums);
+ new Toast('Item Numbers Copied to Clipboard!');
+ } catch (error) {
+ // console.log(error);
+ new Toast('Unable to copy numbers, please check table formatting!');
+ }
+});
+
+document.getElementById('batch-items-textinput').addEventListener('paste', (e) => {
+ setTimeout(() => {
+ const paste = e.target.value;
+ const table = document.getElementById('batch-items-table-div');
+ table.innerHTML = convertToTable(paste, 'batch-items-table');
+
+ document.getElementById('batch-copy-nums').disabled = false;
+
+ document.getElementById('batch-upload-status-text').innerHTML = 'Paste detected! Edit table if needed and click upload.';
+ e.target.value = '';
+ }, 0);
+});
+document.getElementById('batch-upload-btn').addEventListener('click', () => {
+ try {
+ itemsToUpload = getItemsFromTable('batch-items-table');
+ } catch (error) {
+ itemsToUpload = [];
+ document.getElementById('batch-upload-status-text').innerHTML = `Error, check table format! (${error})`;
+ return;
+ }
+
+ if (itemsToUpload.length > 0) {
+ itemsToUpload.forEach((value, idx) => {
+ if (value) {
+ updateItemStatus('loading', idx + 1);
+ }
+ });
+ batchUploadItems(itemsToUpload);
+ return;
+ } else {
+ document.getElementById('batch-upload-status-text').innerHTML = 'No valid items to upload!';
+ }
+
+ return;
+});
+document.getElementById('batch-paste-btn').addEventListener('click', async () => {
+ const text = await navigator.clipboard.readText();
+ const pasteEvent = new Event('paste', {'bubbles': true, 'cancelable': false});
+ const textinput = document.getElementById('batch-items-textinput');
+
+ textinput.value = text;
+ textinput.dispatchEvent(pasteEvent);
+});
+document.getElementById('batch-copy-headers-btn').addEventListener('click', () => {
+ const copyText = `Maximo\tDescription\tIssue Unit\tCommodity Group\tGL Class\tSite\tStoreroom\tVendor\tCatalogue Number\n\t`;
+ navigator.clipboard.writeText(copyText);
+ new Toast('Table copied to clipboard!');
+});
+// dark theme toggle
+document.getElementById('dark-mode-switch').addEventListener('click', toggleTheme);
+// Infinite scroll
+
+// listener for enter key on search field
+document.getElementById('maximo-desc').addEventListener('keyup', function(event) {
+ // Number 13 is the "Enter" key on the keyboard
+ if (event.key === 'Enter') {
+ // Cancel the default action, if needed
+ event.preventDefault();
+ // Trigger the button element with a click
+ validSingle();
+ }
+});
+
+document.getElementById('interact-num').addEventListener('keyup', function(event) {
+ // Number 13 is the "Enter" key on the keyboard
+ if (event.key === 'Enter') {
+ // Cancel the default action, if needed
+ event.preventDefault();
+ // Trigger the button element with a click
+ loadItem();
+ }
+});
+
+function pauseAuto() {
+ document.getElementById('modeSelect').checked = true;
+}
+
+function loadItem() {
+ const itemnum = document.getElementById('interact-num').value.trim();
+ new Toast(`Loading Item: ${itemnum}`);
+ const worker = new WorkerHandler();
+ worker.work(['loadItem', itemnum], showItem);
+}
+
+function auto_grow(elementID) {
+ const element = document.getElementById(elementID);
+ element.style.height = '5px';
+ element.style.height = (element.scrollHeight) + 'px';
+}
+
+function showItem(data) {
+ document.getElementById('maximo-desc').value = data[0].description;
+ document.getElementById('uom-field').value = data[0].uom;
+ document.getElementById('com-group').value = data[0].commodity_group;
+ document.getElementById('gl-class').value = data[0].gl_class;
+}
+
+function writeDescription() {
+ const valid = new Validate();
+ const field = document.getElementById('maximo-desc');
+ if (field.value.length > 0) {
+ const bar = new ProgressBar();
+ bar.update(0, 'Writing asset description to file');
+ let desc = field.value.split(',');
+ desc = valid.assembleDescription(desc);
+ const params = worksheetParams();
+ params.outRow = document.getElementById('current-row').innerHTML;
+ const worker = new WorkerHandler();
+ worker.work(['writeDesc', [params, desc]], writeComplete);
+ } else {
+ new Toast('Please enter a valid description');
+ }
+}
+
+function worksheetParams(path = false) {
+ const params = {
+ // input parameters
+ wsName: document.getElementById('ws-name').value || 'Sheet2', // name of ws
+ inDesc: (document.getElementById('input-col').value || 'F').toUpperCase().split(','), // description columns for input
+ startRow: document.getElementById('start-row').value || '2', // starting row of ws
+ // output parameters
+ outItemNum: document.getElementById('output-col').value.toUpperCase() || 'E',
+ outItemDesc: (document.getElementById('output-col-desc').value || 'F,G,H').toUpperCase().split(','),
+ outComm: document.getElementById('interact-num').value.toUpperCase() || 'I', // commodity group out
+ outGL: document.getElementById('interact-num').value.toUpperCase() || 'J', // gl class out
+ outUOM: document.getElementById('interact-num').value.toUpperCase() || 'K', // uom out
+ outQuestion: document.getElementById('interact-num').value.toUpperCase() || 'L', // questions out
+ outTranslate: document.getElementById('output-col-translation').value.toUpperCase() || 'L',
+ outMissing: document.getElementById('output-col-missing').value.toUpperCase() || 'K',
+ // output data
+ itemNum: document.getElementById('interact-num').value || '999TEST',
+ itemDesc: document.getElementById('maximo-desc').value || 'TEST,ITEM,DESCRIPTION',
+ commGroup: document.getElementById('com-group').value || '401', // commodity group in
+ glClass: document.getElementById('gl-class').value || '6200000000000', // gl class in
+ uom: document.getElementById('uom-field').value || 'EA', // uom in
+ };
+ if (path) {
+ params.filePath = path;
+ } else {
+ params.filePath = document.getElementById('worksheet-path').value;
+ }
+ return params;
+}
+
+function writeItemNum() {
+ const num = document.getElementById('interact-num').value;
+ if (num.length > 0) {
+ const bar = new ProgressBar();
+ bar.update(0, 'Writing item number to file');
+ const path = document.getElementById('worksheet-path').value;
+ const wsName = document.getElementById('ws-name').value;
+ const rowNum = document.getElementById('current-row').innerHTML;
+ const cols = document.getElementById('output-col').value;
+ const worker = new WorkerHandler();
+ worker.work(['writeNum', [path, wsName, rowNum, cols, num]], writeComplete);
+ } else {
+ new Toast('Please enter a valid item number');
+ }
+}
+
+function writeComplete() {
+ const rowNum = parseInt(document.getElementById('current-row').innerHTML);
+ new Toast(`Row ${rowNum} saved!`);
+ document.getElementById('interact-num').value = '';
+ interactiveGoNext(Number(rowNum) + 1);
+}
+
+function openFile(pathElement) {
+ const validFile = document.getElementById(pathElement);
+ const filePath = validFile.value;
+ if (filePath !== 'No file chosen') {
+ new Toast('Opening File in Excel!');
+ shell.openExternal(filePath);
+ }
+}
+
+// Deprecated function, unused.
+function openSettings() {
+ ipcRenderer.send('openSettings');
+ // sendsync blocks parent window...
+ // https://github.com/electron/electron/issues/10426
+}
+
+function openExcel() {
+ document.getElementById('input-col').value = document.getElementById('input-col').value.toUpperCase();
+ document.getElementById('output-col').value = document.getElementById('output-col').value.toUpperCase();
+
+ ipcRenderer.invoke('select-to-be-translated', 'finished').then((result) => {
+ if (!result.canceled) {
+ const worker = new WorkerHandler();
+ const params = worksheetParams(result.filePaths[0]);
+ worker.work(['interactive', params], finishLoadingBatch);
+ document.getElementById('worksheet-path').value = result.filePaths[0];
+ } else {
+ new Toast('File Picker Cancelled');
+ }
+ });
+}
+
+// BATCH UPLOAD FUNCTIONS
+/**
+ * Reads a table and generates items from it
+ *
+ * @param {string} tableId the HTML id of the table to read
+ * @return {Array} an array of Items
+ */
+function getItemsFromTable(tableId) {
+ colLoc = {
+ description: -1,
+ uom: -1,
+ commGroup: -1,
+ glClass: -1,
+ maximo: -1,
+ vendor: -1,
+ storeroom: -1,
+ catNum: -1,
+ siteID: -1,
+ };
+
+ const table = document.getElementById(`${tableId}`);
+ // find Description, UOM, Commodity Group, and GL Class
+ const rows = parseInt(table.getAttribute('data-rows'));
+ const cols = parseInt(table.getAttribute('data-cols'));
+ // iniitalize items array
+ const items = [];
+ // go through first row to find headings.
+ let validParams = 0;
+ for (let i = 1; i <= cols; i++) {
+ // get a cell in the table by its id
+ const cell = document.getElementById('1-' + i);
+
+ // see if cell value matches any of the required parameters to create an item object
+ if (cell.innerHTML.toUpperCase() === 'DESCRIPTION') {
+ colLoc.description = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'UOM' || cell.innerHTML.toUpperCase() === 'ISSUE UNIT') {
+ colLoc.uom = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'COMMODITY GROUP' || cell.innerHTML.toUpperCase() === 'COMM GROUP') {
+ colLoc.commGroup = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'GL CLASS') {
+ colLoc.glClass = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'SITEID' || cell.innerHTML.toUpperCase() === 'SITE') {
+ colLoc.siteID = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'STOREROOM' || cell.innerHTML.toUpperCase() === 'STOREROOM') {
+ colLoc.storeroom = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'VENDOR' || cell.innerHTML.toUpperCase() === 'VENDOR NUMBER') {
+ colLoc.vendor = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'CAT NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOG NUMBER' || cell.innerHTML.toUpperCase() === 'CATALOGUE NUMBER') {
+ colLoc.catNum = i;
+ validParams++;
+ } else if (cell.innerHTML.toUpperCase() === 'MAXIMO' || cell.innerHTML.toUpperCase() === 'ITEM NUMBER') {
+ colLoc.maximo = i;
+ validParams++;
+ }
+ // console.log(validParams)
+ }
+
+ // Checking if mandatory columns are filled
+ if (colLoc.siteID != -1 || colLoc.storeroom != -1 || colLoc.vendor != -1 || colLoc.catNum != -1) {
+ if (colLoc.siteID == -1 || colLoc.storeroom == -1) {
+ let numMissing = 0;
+ let missingCols = '';
+ const missingColArr = [];
+ console.log('missing params');
+ for (const property in colLoc) {
+ if (colLoc[property] == -1 && property != 'vendor' && property != 'catNum') {
+ console.log(property);
+ numMissing++;
+ missingColArr.push(property.toLowerCase());
+ }
+ }
+ missingCols = missingColArr.join(', ');
+ document.getElementById('batch-upload-status-text').innerHTML = `Table is missing ${numMissing} column(s): (${missingCols}). Table will not be uploaded!`;
+ return;
+ }
+ } else {
+ if (validParams < 5) {
+ let missingCols = '';
+ const missingColArr = [];
+ console.log('missing params');
+ for (const property in colLoc) {
+ if (colLoc[property] == -1 && property != 'siteID' && property != 'storeroom' && property != 'vendor' && property != 'catNum') {
+ console.log(property);
+ missingColArr.push(property.toLowerCase());
+ }
+ }
+ missingCols = missingColArr.join(', ');
+ document.getElementById('batch-upload-status-text').innerHTML = `Table is missing ${5 - validParams} column(s): (${missingCols}). Table will not be uploaded!`;
+ return;
+ }
+ }
+ let invalidItems = 0;
+ // Make item for request that includes inventory upload
+ if (validParams > 5) {
+ let site = undefined;
+ let storeroom = undefined;
+ let vendor = undefined;
+ let catNum = undefined;
+ for (let i = 2; i <= rows; i++) {
+ const desc = sanitizeString(document.getElementById(i + '-' + colLoc.description).innerHTML);
+ const uom = sanitizeString(document.getElementById(i + '-' + colLoc.uom).innerHTML).toUpperCase();
+ const commGroup = sanitizeString(document.getElementById(i + '-' + colLoc.commGroup).innerHTML);
+ const glclass = sanitizeString(document.getElementById(i + '-' + colLoc.glClass).innerHTML).toUpperCase();
+ if (colLoc.siteID != -1) {
+ site = sanitizeString(document.getElementById(i + '-' + colLoc.siteID).innerHTML).toUpperCase();
+ }
+ if (colLoc.storeroom != -1) {
+ storeroom = sanitizeString(document.getElementById(i + '-' + colLoc.storeroom).innerHTML).toUpperCase();
+ }
+ if (colLoc.vendor != -1) {
+ vendor = sanitizeString(document.getElementById(i + '-' + colLoc.vendor).innerHTML);
+ }
+ if (colLoc.catNum != -1) {
+ catNum = sanitizeString(document.getElementById(i + '-' + colLoc.catNum).innerHTML);
+ }
+ const maximo = sanitizeString(document.getElementById(i + '-' + colLoc.maximo).innerHTML);
+ // if all required parameters are not available, don't create the item and move to next row
+ if (desc == '' || uom == '' || commGroup == '' || glclass == '' || desc == 0 || uom == 0 || commGroup == 0 || glclass == 0 || site == '' || storeroom == '') {
+ updateItemStatus('error', (i - 1));
+ items.push('');
+ invalidItems++;
+ continue;
+ }
+
+ const item = new Item(undefined, desc, uom, commGroup, glclass, site, storeroom, vendor, catNum);
+ if (colLoc.maximo != -1 && maximo != 0 && maximo.toString().length === 7) {
+ item.itemnumber = maximo;
+ } else if (desc.toUpperCase().includes('DWG')) {
+ item.series = 98;
+ } else if (commGroup == '490' && glclass == 'PLS') {
+ // Change when when item num reachs 9920000
+ item.series = 991;
+ }
+ // console.log(item);
+ // add the item to the array
+ items.push(item);
+ }
+ }
+ // Make item for request that doesn't need inventory upload
+ else {
+ for (let i = 2; i <= rows; i++) {
+ const desc = sanitizeString(document.getElementById(i + '-' + colLoc.description).innerHTML);
+ const uom = sanitizeString(document.getElementById(i + '-' + colLoc.uom).innerHTML).toUpperCase();
+ const commGroup = sanitizeString(document.getElementById(i + '-' + colLoc.commGroup).innerHTML);
+ const glclass = sanitizeString(document.getElementById(i + '-' + colLoc.glClass).innerHTML).toUpperCase();
+ const maximo = sanitizeString(document.getElementById(i + '-' + colLoc.maximo).innerHTML);
+ // if all required parameters are not available, don't create the item and move to next row
+ if (desc == '' || uom == '' || commGroup == '' || glclass == '' || desc == 0 || uom == 0 || commGroup == 0 || glclass == 0) {
+ updateItemStatus('error', (i - 1));
+ items.push('');
+ invalidItems++;
+ continue;
+ }
+ const item = new Item(undefined, desc, uom, commGroup, glclass);
+ if (colLoc.maximo != -1 && maximo != 0 && maximo.toString().length === 7) {
+ item.itemnumber = maximo;
+ } else if (desc.toUpperCase().includes('DWG')) {
+ item.series = 98;
+ } else if (commGroup == '490' && glclass == 'PLS') {
+ // Change when when item num reachs 9920000
+ item.series = 991;
+ }
+ // console.log(item);
+ // add the item to the array
+ items.push(item);
+ }
+ }
+
+ if (invalidItems > 0) {
+ document.getElementById('batch-upload-status-text').innerHTML = `Warning! ${invalidItems} invalid items will not be uploaded`;
+ }
+ // return the item array
+ return items;
+}
+
+/**
+ * Uploads an item from item information accordion dropdown (single item upload)
+ *
+ */
+async function uploadItem() {
+ document.getElementById('confirm-btn').innerHTML = ' Uploading...';
+ document.getElementById('confirm-btn').disabled = true;
+ const worker = new WorkerHandler();
+ const item = new Item(
+ sanitizeString(document.getElementById('interact-num').value),
+ sanitizeString(document.getElementById('maximo-desc').value),
+ sanitizeString(document.getElementById('uom-field').value),
+ sanitizeString(document.getElementById('com-group').value),
+ sanitizeString(document.getElementById('gl-class').value),
+ );
+
+ if (document.getElementById('long-desc').value.length > 0) {
+ item.longdescription = document.getElementById('long-desc').value;
+ }
+
+
+ worker.work(['uploadItems', [item]], (e) => {
+ console.log(e);
+ if (e === undefined || typeof e != 'string' || e == 200) {
+ document.getElementById('error').innerHTML = 'Upload Success';
+ document.getElementById('confirm-btn').innerHTML = 'Upload Item';
+ document.getElementById('confirm-btn').disabled = false;
+ new Toast('Upload Complete!', 'bg-success');
+ const itemUrl = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${item.itemnumber}`;
+ document.getElementById('error').innerHTML = `Item Upload Successful! (Click to view item) `;
+ document.getElementById('item-link').addEventListener('click', function(x) {
+ x.preventDefault();
+ shell.openExternal(itemUrl);
+ });
+ } else {
+ document.getElementById('error').innerHTML = 'Upload Fail';
+ document.getElementById('confirm-btn').innerHTML = 'Upload Item';
+ document.getElementById('confirm-btn').disabled = false;
+ // TODO: fail messages
+ document.getElementById('error').innerHTML = `Item Upload Failed! ${e}`;
+ }
+ });
+}
+/**
+ * Uploads an array of items
+ *
+ * @param {Array} items
+ */
+async function batchUploadItems(items) {
+ const worker = new WorkerHandler();
+ // disable clear and upload buttons while uploading items to prevent duplicate requests
+ const btn = document.getElementById('batch-upload-btn');
+ const clearBtn = document.getElementById('clear-batch-items-btn');
+ clearBtn.disabled = true;
+ btn.disabled = true;
+
+ worker.work(['uploadItems', items, true], (e) => {
+ let finishText = `Upload Finished! ${e[2]} items uploaded, ${e[3]} items added to inventory. `;
+ if (e[0] == 'failure') {
+ new Toast(`Invalid! ${e[1]}}!`);
+ }
+ clearBtn.disabled = false;
+ btn.disabled = false;
+ updateItemNums(e[0]);
+ const rows = parseInt(document.getElementById('batch-items-table').getAttribute('data-rows')) - 1;
+ let nums = '';
+ for (let i = 2; i <= rows + 1; i++) {
+ nums += document.getElementById(`${i}-${colLoc.maximo}`).innerHTML ? (document.getElementById(`${i}-${colLoc.maximo}`).innerHTML + ',') : '';
+ }
+ if (e[2] > 0) {
+ const itemUrl = `https://${CONSTANTS.ENV}.iko.max-it-eam.com/maximo/oslc/graphite/manage-shell/index.html?event=loadapp&value=item&additionalevent=useqbe&additionaleventvalue=itemnum=${nums}`;
+ finishText += `Click to view:`;
+ document.getElementById('batch-upload-status-text').innerHTML = finishText;
+ document.getElementById('batch-link').addEventListener('click', function(e) {
+ e.preventDefault();
+ shell.openExternal(itemUrl);
+ });
+ } else {
+ document.getElementById('batch-upload-status-text').innerHTML = finishText;
+ }
+ console.log('upload finished');
+ });
+}
+/**
+ * Gets a list of newly generated item nums and updates the table with them.
+ *
+ * If an item has just been uploaded, populates item num cell with new number.
+ *
+ * @param {int[][]} arr array of pairs of item nums and table row indexes
+ */
+function updateItemNums(arr) {
+ for (const pair of arr) {
+ const itemNum = pair[0];
+ const itemRowIndex = pair[1];
+
+ // update item number cell
+ const cell = document.getElementById(`${itemRowIndex + 1}-${colLoc.maximo}`);
+ cell.innerHTML = itemNum;
+
+ // highlight the item number yellow to signify that it was newly uploaded
+ cell.classList.add('table-alert');
+ }
+}
+// //////////////////////
+
+function skipRow() {
+ const row = document.getElementById('current-row').innerHTML;
+ interactiveGoNext(Number(row) + 1);
+}
+
+function finishLoadingBatch(params) {
+ const bar = new ProgressBar();
+ // this has a special work thread since initializing a worker thread takes ~700 ms which is too long
+ document.getElementById('valid-row').innerHTML = params[1];
+ document.getElementById('total-row').innerHTML = params[2];
+ const worker = new Worker('./worker.js');
+ const db = new Database();
+ let description = db.getDescription(params[0]);
+ if (description === undefined) {
+ bar.update(100, 'Done!');
+ worker.terminate();
+ new Toast('Finished Batch Processing');
+ return false;
+ }
+ bar.update(0, 'Processing Descriptions');
+ processBatch(worker, params[0], description);
+ worker.onmessage = (msg) => {
+ if (msg.data[0] === 'nextrow') {
+ description = db.getDescription(msg.data[1]);
+ if (description === undefined) {
+ params = worksheetParams(document.getElementById('worksheet-path').value);
+ worker.postMessage([
+ 'saveProcessed',
+ [params, msg.data[1]],
+ ]);
+ new Toast('Finished Batch Processing');
+ new Toast('Please wait for file to finish saving...');
+ return false;
+ }
+ document.getElementById('current-row').innerHTML = description.row;
+ bar.update(msg.data[1] / params[2] * 100, `Processing Description. Row: ${msg.data[1]} of ${params[2]}`);
+ processBatch(worker, msg.data[1], description);
+ } else if (msg.data[0] === 'saveComplete') {
+ interactiveGoNext(msg.data[1]);
+ new Toast('File Saved');
+ worker.terminate();
+ } else {
+ console.log(`IDK: ${msg.data}`);
+ }
+ };
+}
+
+function processBatch(worker, row, description) {
+ const interactive = document.getElementById('modeSelect').checked;
+ const related = document.getElementById('relatedSelect').checked;
+ const translate = document.getElementById('translateSelect').checked;
+ const params = worksheetParams(document.getElementById('worksheet-path').value);
+ if (interactive) {
+ new Toast('Pausing / Switching to Interactive Mode');
+ worker.postMessage([
+ 'saveProcessed',
+ [params, row],
+ ]);
+ } else {
+ worker.postMessage([
+ 'nonInteractive',
+ [
+ related,
+ translate,
+ description.description,
+ document.getElementById('selected-language').value,
+ params,
+ row,
+ ],
+ ]);
+ }
+}
+
+function continueAuto() {
+ document.getElementById('modeSelect').checked = false;
+ finishLoadingBatch([
+ Number(document.getElementById('current-row').innerHTML),
+ document.getElementById('valid-row').innerHTML,
+ document.getElementById('total-row').innerHTML,
+ ]);
+}
+
+function interactiveGoNext(row) {
+ const bar = new ProgressBar();
+ const db = new Database();
+ const description = db.getDescription(row);
+ if (description === undefined) {
+ bar.update(100, 'Done!');
+ new Toast('End of File Reached');
+ return false;
+ }
+ document.getElementById('current-row').innerHTML = description.row;
+ if (description) {
+ const worker = new WorkerHandler();
+ document.getElementById('maximo-desc').value = description.description;
+ worker.work(['validSingle', description.description], showResult);
+ } else {
+ const field = document.getElementById('maximo-desc');
+ field.placeholder = 'Row is blank, press skip row to go next';
+ field.value = '';
+ const bar = new ProgressBar();
+ bar.update(100, 'Done');
+ }
+}
+
+function validSingle(isExtended = false) {
+ const bar = new ProgressBar();
+ bar.update(0, 'Starting Item Description Validation');
+ const raw_desc = document.getElementById('maximo-desc').value;
+ const worker = new WorkerHandler();
+ worker.work(['validSingle', raw_desc], (result) => {
+ showResult(result, isExtended);
+ });
+}
+
+function showResult(result, isExtended = false) {
+ let triDesc = document.getElementById('result-triple-main');
+ triDesc.value = result[0][0];
+ triDesc = document.getElementById('result-triple-ext1');
+ triDesc.value = result[0][1];
+ triDesc = document.getElementById('result-triple-ext2');
+ triDesc.value = result[0][2];
+ const related = document.getElementById('relatedSelect').checked;
+ const translate = document.getElementById('translateSelect').checked;
+ calcConfidence(result[0][3]);
+ document.getElementById('validate-badge').innerHTML = 'New';
+ if (translate) {
+ translationDescription(result[0][3]);
+ }
+ if (related) {
+ findRelated(result[0], isExtended);
+ }
+}
+
+async function ItemAnalysis() {
+ const valid = new Validate();
+ const raw_desc = document.getElementById('maximo-desc').value;
+ const result = await valid.validateSingle(raw_desc);
+ let triDesc = document.getElementById('result-triple-main');
+ triDesc.value = result[0];
+ triDesc = document.getElementById('result-triple-ext1');
+ triDesc.value = result[1];
+ triDesc = document.getElementById('result-triple-ext2');
+ triDesc.value = result[2];
+ calcConfidence(result[3]);
+}
+
+function findRelated(result, isExtended = false) {
+ const worker = new WorkerHandler();
+ worker.work(['findRelated', result[3], isExtended], (result) => {
+ showRelated(result, isExtended);
+ });
+}
+
+function translationDescription(description) {
+ // for now do not translate if english is selected
+ if (document.getElementById('selected-language').value != 'en') {
+ const worker = new WorkerHandler();
+ if (document.getElementById('result-triple-ext1').value) {
+ description = `${document.getElementById('result-triple-main').value},${document.getElementById('result-triple-ext1').value}`;
+ } else {
+ description = document.getElementById('result-triple-main').value;
+ }
+
+ worker.work([
+ 'translateItem',
+ description,
+ document.getElementById('selected-language').value,
+ 'post',
+ ], displayTranslation);
+ } else {
+ new Toast('Currently translation into English is not supported');
+ }
+}
+
+function displayTranslation(data) {
+ document.getElementById('trans-desc').value = data[0];
+ document.getElementById('translation-description').value = `The following words do not have a translation:\n${data[1]}\nPlease check logs at bottom of page for details`;
+ auto_grow('translation-description');
+}
+
+function calcConfidence(data) {
+ let description;
+ let level = 0;
+ let tree = '';
+ let parent = 0;
+ const regex = /\d+/g;
+ const db = new Database();
+ let analysis;
+ let result = '';
+ const option = {
+ style: 'percent',
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1,
+ };
+ const formatter = new Intl.NumberFormat('en-US', option);
+
+ if (data?.length > 0) { // test if description is blank
+ description = data.split(',');
+ for (let j = 0; j < description.length; j++) {
+ if (!(description[j].match(regex))) {
+ if (db.isManufacturer(description[j])) {
+ result = `${result}\n${description[j]} is confirmed as a manufacturer`;
+ } else {
+ level++;
+ if (level == 1) {
+ tree = description[j];
+ } else {
+ tree = tree + ',' + description[j];
+ }
+ analysis = db.getAnalysis(tree);
+ if (analysis) {
+ if (level == 1) {
+ if (analysis.count >= 100) {
+ result = `${description[j]}: is COMMONLY used as an Item Type.\n${analysis.count}: occurrences found`;
+ } else if (analysis.count >= 20) {
+ result = `${description[j]}: is SOMETIMES used as an Item Type.\n${analysis.count}: occurrences found`;
+ } else {
+ result = `WARNING: ${description[j]}: is an UNCOMMON Item Type.\nPlease double check.\n${analysis.count}: occurrences found`;
+ }
+ } else {
+ if (analysis.count / parent >= 0.25) {
+ result = `${result}\n${description[j]} is COMMONLY used as an item descriptor for ${tree}.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
+ } else if (analysis.count / parent >= 0.05) {
+ result = `${result}\n${description[j]} is SOMETIMES used as an item descriptor for ${tree}.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
+ } else {
+ result = `${result}\n${description[j]} is an UNCOMMON item descriptor for ${tree}.\nPlease double check.\n${analysis.count} of ${parent} = ${formatter.format(analysis.count / parent)}`;
+ }
+ }
+ parent = analysis.count;
+ } else {
+ result = `${result}\n${description[j]}: Does not exist in Maximo as part of: ${tree}.\nPlease Check with Corporate`;
+ }
+ }
+ }
+ }
+ document.getElementById('valid-description').value = result.trim();
+ } else {
+ new Toast('Blank Description');
+ }
+}
+
+/**
+ * Initializes search results table and populates relatedResults object
+ * with search results.
+ *
+ * @param {Array< Map>,Map,String>} result array of [array of item nums of all search results, map of item nums to descriptions, and search query with words separated by commas]
+ * @param {bool} isExtended whether the user has clicked extended search
+ */
+async function showRelated(result, isExtended = false) {
+ const bar = new ProgressBar();
+ if (!result[0]) {
+ bar.update(100, 'Done!');
+ return false;
+ }
+
+ // reverse results to get newest items first.
+ // technically this isn't the best way to do
+ // this because results aren't guaranteed
+ // to be in order of oldest to newest.
+ for (const [key, value] of Object.entries(result[0])) {
+ result[0][key] = result[0][key].reverse();
+ }
+ // populate global variable with search results (bad practice, but it works)
+ relatedResults = {
+ idx: 0,
+ curKey: 0,
+ results: result,
+ };
+
+ // reset table after called
+ const relatedTable = document.getElementById('related-table');
+ const numResultsText = document.getElementById('num-results');
+
+ if (isExtended) {
+ relatedTable.classList.add(`isExt`);
+ } else {
+ if (relatedTable.classList.contains(`isExt`)) {
+ relatedTable.classList.remove(`isExt`);
+ }
+ }
+ // Add headings to the search results table
+ relatedTable.innerHTML = `
+
+
+
+
Percent Match
+
Item Number
+
Item Description
+ ${(isExtended ? '
More Info
' : '')}
+
UOM
+
C_Group
+
GL_Class
+
+
+
+
+
+ `;
+
+ numResultsText.innerHTML = `Found ${Object.entries(result[1]).length} results`;
+
+ // expand the search results accordion
+ document.getElementById('related-items-accordion-btn').classList.remove('collapsed');
+ // load a couple of items
+ loadRelated();
+ html = new bootstrap.Collapse(document.getElementById('accordion-relatedItem'), {toggle: false});
+ html.show();
+ bar.update(100, 'Done!');
+}
+
+function loadRelated() {
+ // check if user clicked extended search
+ const isExtended = document.getElementById('related-table').classList.contains('isExt');
+
+ // a map with percent match as key (in decimal form) and array of items as value
+ // for example, if the key is 1, the list of items match with the search query 100%. If the key is 0.5, the list of items match with the search query 50%.
+ const scores = relatedResults.results[0];
+
+ // relatedResults.idx is like a bookmark. It keeps track of how many items have been loaded from the array of items associated with the current key in scores.
+ // relatedResults.curKey is the number of the current key that is being loaded if you were to iterate thru the keys in scores.
+ // For example, the first key's number would be 0, second key 1, etc.
+ if (relatedResults.curKey >= Object.entries(scores).length) {
+ // If curKey is equal or larger than the number of keys in scores, then there are no more items to load, so return
+ return;
+ } else if (Object.entries(scores)[relatedResults.curKey][1].length == 0) {
+ // If there are no items associated with the current key, then move to the next key and try loading items again.
+ relatedResults.curKey++; // increment curKey so that the next time the function runs, it will try to load items from the next key
+ relatedResults.idx = 0; // reset idx so that it starts from the beginning of the array
+ loadRelated();
+ return; // return so we don't do make an infinite loop
+ }
+
+ const step = 20; // number of items to load at once.
+
+ // get arrs from results obj
+ const itemNames = relatedResults.results[1]; // a map with the 9-series number of the item as the key and the item info as value. Item info is an array with 4 items: [description, gl class, uom, commodity group]
+ const searchWords = relatedResults.results[2].split(','); // an array of the search query split by commas. For example, if the search query is "test, item, description", then searchWords would be ["test", "item", "description"]
+
+ let html = ''; let color = ''; // html is the html that will be added to the search results table. color is the color of the row in the table.
+
+ let itemDescription;
+
+ // formatting options for percent match. Converts decimal to percent and rounds to nearest whole number.
+ const option = {
+ style: 'percent',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ };
+
+ const formatter = new Intl.NumberFormat('en-US', option);
+ // technically this is bad practise since object order might not be guarenteed
+ // https://stackoverflow.com/questions/983267/how-to-access-the-first-property-of-a-javascript-object
+
+ const percentMatch = Object.entries(scores)[relatedResults.curKey][0]; // get the percent match (name of the current key)
+ const itemNumList = Object.entries(scores)[relatedResults.curKey][1]; // get the array of items associated with key
+ let itemsToLoad; // array of items to load
+
+ if (relatedResults.idx + step >= itemNumList.length) {
+ // if there are less than 20 items to load, load the remaining items in value and increment curKey and reset idx
+ // this way, the next time the function is called, the next key will be loaded instead
+ itemsToLoad = itemNumList.slice(relatedResults.idx, undefined); // get array of items from idx to end of array
+ relatedResults.curKey++;
+ relatedResults.idx = 0;
+ } else {
+ itemsToLoad = itemNumList.slice(relatedResults.idx, relatedResults.idx + step);
+ relatedResults.idx += step;
+ }
+
+ // iterate thru each item in value array
+ for (const itemNum of itemsToLoad) {
+ itemDescription = itemNames[itemNum][0];
+ if (itemDescription) {
+ // Bold all words in item description that match the search query
+ for (const word of searchWords) {
+ split = word.split(' ');
+ for (const smallWord of split) {
+ if (smallWord.length > 0) {
+ itemDescription = itemDescription.replace(
+ new RegExp(`${smallWord}`, 'i'),
+ `${itemDescription.match(new RegExp(`${smallWord}`, 'i'))?.[0]}`,
+ );
+ }
+ }
+ }
+ // set row color based on percent match
+ if (percentMatch > 0.7) {
+ color = 'table-success'; // green
+ } else if (percentMatch > 0.4) {
+ color = 'table-warning'; // yellow
+ } else {
+ color = 'table-danger'; // red
+ }
+
+ // create HTML row.
+ // In extended search, the vendor info is split from the item description by a | (pipe character).
+ // All info after the pipe character is put into another column.
+ // If the item description does not have a pipe character, then the second column is not loaded.
+ html = `${html}\n
`;
+ }
+ }
+
+ // add html to table
+ const relatedTable = document.getElementById('related-items');
+ relatedTable.innerHTML += html;
+
+ // if less than 5 items loaded, load more
+ if (itemsToLoad.length < 5) {
+ document.getElementById('everything').dispatchEvent(new Event('scroll'));
+ }
+}
+
+// unused function (was used to copy item validation): probably remove this
+function copyResult(copy) {
+ if (copy === 'single') {
+ const content = document.getElementById('result-single').innerText;
+ clipboard.writeText(content);
+ new Toast('Single Description Copied to Clipboard!');
+ } else {
+ const desc = [];
+ let content = '';
+ content = document.getElementById('result-triple-main').innerText;
+ desc.push(content);
+ content = document.getElementById('result-triple-ext1').innerText;
+ desc.push(content);
+ content = document.getElementById('result-triple-ext2').innerText;
+ desc.push(content);
+ clipboard.write({
+ text: document.getElementById('result-single').innerText,
+ html: `
${desc[0]}
${desc[1]}
${desc[2]}
`,
+ });
+ new Toast('Triple Description Copied to Clipboard!');
+ }
+}
diff --git a/renderer/item_translation.html b/src/renderer/item_translation.html
similarity index 100%
rename from renderer/item_translation.html
rename to src/renderer/item_translation.html
diff --git a/renderer/item_translation.js b/src/renderer/item_translation.js
similarity index 100%
rename from renderer/item_translation.js
rename to src/renderer/item_translation.js
diff --git a/renderer/observation_template.html b/src/renderer/observation_template.html
similarity index 100%
rename from renderer/observation_template.html
rename to src/renderer/observation_template.html
diff --git a/renderer/observation_template.js b/src/renderer/observation_template.js
similarity index 98%
rename from renderer/observation_template.js
rename to src/renderer/observation_template.js
index a9d5515..620091d 100644
--- a/renderer/observation_template.js
+++ b/src/renderer/observation_template.js
@@ -1,5 +1,5 @@
const { dialog } = require('electron').remote
-const ObservationDatabase = require('../assets/better-sqlite');
+const ObservationDatabase = require('../misc/better-sqlite');
// Debug stuff
// document.getElementById("selected_output").innerHTML = 'C:\\Users\\majona\\Documents\\observationList\\results.xlsx'
diff --git a/renderer/setting.html b/src/renderer/setting.html
similarity index 100%
rename from renderer/setting.html
rename to src/renderer/setting.html
diff --git a/renderer/setting.js b/src/renderer/setting.js
similarity index 100%
rename from renderer/setting.js
rename to src/renderer/setting.js
diff --git a/renderer/start_page.html b/src/renderer/start_page.html
similarity index 97%
rename from renderer/start_page.html
rename to src/renderer/start_page.html
index de1b278..794b0ac 100644
--- a/renderer/start_page.html
+++ b/src/renderer/start_page.html
@@ -8,8 +8,8 @@
EAM Spare Parts Tool
-
-
+
+
diff --git a/renderer/start_page.js b/src/renderer/start_page.js
similarity index 100%
rename from renderer/start_page.js
rename to src/renderer/start_page.js
diff --git a/renderer/style.css b/src/renderer/style.css
similarity index 89%
rename from renderer/style.css
rename to src/renderer/style.css
index 4e47659..31ea42d 100644
--- a/renderer/style.css
+++ b/src/renderer/style.css
@@ -1,6 +1,6 @@
@charset "UTF-8";
@import "table_style.css";
-@import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
+@import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
/*
--> custom styles for elements
@@ -23,7 +23,7 @@ text-align: left;
box-shadow: none;
transition:all 0.2s ease-in-out;
transform: scale(2);
- background-image: url("sun.png");
+ background-image: url("../../assets/moon.png");
background-color: #cfe2ff;
outline-offset: -1px;
outline: 2px solid #cfe2ff;
@@ -34,7 +34,7 @@ text-align: left;
background-color: #212529;
outline: 2px solid #212529;
border-color: #212529;
- background-image: url("moon.png");
+ background-image: url("../../assets/moon.png");
}
input {
@@ -63,14 +63,14 @@ textarea {
font-family: "Material Icons";
font-style: normal;
font-weight: 100 700;
- src: url(./MaterialIcons.woff2) format("woff2");
+ src: url(../../assets/MaterialIcons.woff2) format("woff2");
}
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 300;
- src: url(./MaterialIcons.woff2) format("woff2");
+ src: url(../../assets/MaterialIcons.woff2) format("woff2");
}
.material-icons {
diff --git a/renderer/table_style.css b/src/renderer/table_style.css
similarity index 100%
rename from renderer/table_style.css
rename to src/renderer/table_style.css
diff --git a/renderer/worker.js b/src/renderer/worker.js
similarity index 96%
rename from renderer/worker.js
rename to src/renderer/worker.js
index d0a7926..3613253 100644
--- a/renderer/worker.js
+++ b/src/renderer/worker.js
@@ -1,16 +1,16 @@
-const Validate = require('../assets/validators');
-const ExcelReader = require('../assets/spreadsheet');
-const Spreadsheet = require('../assets/exceljs');
-const Database = require('../assets/indexDB');
-const SharedDatabase = require('../assets/sharedDB');
-const Maximo = require('../assets/maximo');
-const AssetTranslate = require('../assets/asset_translation/asset_translation_main.js');
-const ObservationDatabase = require('../assets/better-sqlite');
-const TranslationDatabase = require('../assets/item_translation/item-translation-sqlite');
+const Validate = require('../misc/validators');
+const ExcelReader = require('../misc/spreadsheet');
+const Spreadsheet = require('../misc/exceljs');
+const Database = require('../misc/indexDB');
+const SharedDatabase = require('../misc/sharedDB');
+const Maximo = require('../misc/maximo');
+const AssetTranslate = require('../asset_translation/asset_translation_main.js');
+const ObservationDatabase = require('../misc/better-sqlite');
+const TranslationDatabase = require('../item_translation/item-translation-sqlite');
const path = require('path');
-const Translation = require('../assets/item_translation/item-translation');
+const Translation = require('../item_translation/item-translation');
const fs = require('fs');
-const CONSTANTS = require('../assets/constants.js');
+const CONSTANTS = require('../misc/constants.js');
/**
* Handles messages from the WorkerHandler
*
@@ -330,7 +330,7 @@ async function checkItemCache(version) {
if (!(await shareDB.checkVersion(version))) {
db.createTables();
const filePath = path.join(
- require('path').resolve(__dirname).replace('renderer', 'assets'),
+ require('path').resolve(__dirname).replace('src\\renderer', 'assets'),
'item_information.xlsx',
);
const excel = new ExcelReader(filePath);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..6d1ecc2
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,110 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+ // "resolveJsonModule": true, /* Enable importing .json files. */
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./built", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ "allowUnreachableCode": false, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ },
+ "include": ["./src/**/*"]
+}