import { useStore } from "~/stores/index"; export default defineNuxtPlugin(() => { const store = useStore(); //==========Find & filter================= const find = function (arr, obj, attr) { if (!Array.isArray(arr)) { if (typeof arr === "object") { arr = Object.values(arr); } else { console.error(`arr "${arr}" is not an array`); return; } } const keys = Object.keys(obj); let found = arr.find((v) => { let valid = true; keys.map((key) => { let val = obj[key]; if (valid === false) return false; else if (Array.isArray(val)) { if (val.findIndex((x) => x === v[key]) < 0) valid = false; } else if (!(v[key] === val)) valid = false; }); return valid; }); return found ? (attr ? found[attr] : found) : undefined; }; const findIndex = function (arr, obj) { const keys = Object.keys(obj); return arr.findIndex((v) => { let valid = true; keys.map((key) => { let val = obj[key]; if (valid === false) return false; else if (Array.isArray(val)) { if (val.findIndex((x) => x === v[key]) < 0) valid = false; } else if (!(v[key] === val)) valid = false; }); return valid; }); }; const filter = function (arr, obj, attr) { const keys = Object.keys(obj); if (!arr) { console.error(`$filter: arr is ${arr}`); return []; } let rows = arr.filter((v) => { let valid = true; keys.map((key) => { let val = obj[key]; if (valid === false) return false; else if (Array.isArray(val)) { if (val.findIndex((x) => x === v[key]) < 0) valid = false; } else if (!(v[key] === val)) valid = false; }); return valid; }); return attr ? rows.map((v) => v[attr]) : rows; }; //=========Empty & copy============ const id = function () { return Math.random().toString(36).substring(2, 12).toUpperCase(); }; const empty = function (val) { return val === undefined || val === null || val === ""; }; const toRawDeep = function (observed) { const val = toRaw(observed); if (val instanceof Date) return val; if (Array.isArray(val)) return val.map(toRawDeep); if (val === null) return null; if (typeof val === "object") { const entries = Object.entries(val).map(([key, val]) => [key, toRawDeep(val)]); return Object.fromEntries(entries); } return val; }; const copy = function (val) { return structuredClone(toRawDeep(val)); }; const clone = function (obj) { if (obj === null || typeof obj !== "object" || "isActiveClone" in obj) return obj; if (obj instanceof Date) var temp = new obj.constructor(); //or new Date(obj); else var temp = obj.constructor(); for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj["isActiveClone"] = null; temp[key] = clone(obj[key]); delete obj["isActiveClone"]; } } return temp; }; const remove = function (arr, idx) { arr.splice(idx, 1); }; const stripHtml = function (html, length) { if (!html) return null; else if (typeof html !== "string") return html; if (html ? html.indexOf("<") < 0 : false) { return length ? (html.length > length ? html.substring(0, length) + "..." : html) : html; } var tmp = document.createElement("DIV"); tmp.innerHTML = html; var val = tmp.textContent || tmp.innerText || ""; return length ? (val.length > length ? val.substring(0, length) + "..." : val) : val; }; //==========Convert================= const isNumber = function (val) { if (empty(val)) return false; val = val.toString().replace(/,/g, ""); return !isNaN(val); }; const numtoString = function (val, config = undefined) { if (empty(val)) return "0"; val = val.toString().replace(/,/g, ""); if (isNaN(val)) return; const defaultConfig = { type: "vi-VN", decimal: undefined, mindecimal: undefined, hasUnit: false, }; const finalConfig = { ...defaultConfig, ...config }; const { type, decimal, mindecimal, hasUnit } = finalConfig; const options = {}; if (typeof decimal === "number") options.maximumFractionDigits = decimal || 0; if (mindecimal) options.minimumFractionDigits = mindecimal; const unit = hasUnit ? " đ" : ""; return Number(val).toLocaleString(type, typeof decimal === "number" ? options : undefined) + unit; }; const formatNumber = function (val) { if (empty(val)) return; val = val.toString().replace(/[,\.]/g, ""); if (val.indexOf("%") > 0) { val = val.replace(/%/g, ""); return isNaN(val) ? undefined : Number(val) / 100; } return isNaN(val) ? undefined : Number(val); }; const formatUnit = function (val, unit, decimal, string, mindecimal) { val = formatNumber(val); if (val === undefined) return; let percentage = unit === 0.01 || unit === "0.01" ? "%" : ""; val = unit ? val / Number(unit) : val; let f = { maximumFractionDigits: decimal || 0 }; if (mindecimal) f["minimumFractionDigits"] = mindecimal; return string ? val.toLocaleString("en-EN", f) + percentage : val.toFixed(decimal); }; //==========Calculate================= const calc = function (fn) { return new Function("return " + fn)(); }; const calculate = function (row, tags, formula, decimal, unit) { let val = this.$copy(formula); let valid = 0; tags.forEach((v) => { let myRegExp = new RegExp(v, "g"); let res = this.$formatNumber(row[v]); if (res) valid = 1; val = val.replace(myRegExp, `(${res || 0})`); }); if (valid === 0) return { success: false, value: undefined }; //all values is null //calculate try { let value = this.$calc(val); if (isNaN(value) || value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) { var result = { success: false, value: value }; } else { value = value === true || value === false ? value : formatUnit(value, unit, decimal, true, decimal); var result = { success: true, value: value }; } } catch (err) { var result = { success: false, value: undefined }; } return result; }; const calculateFunc = function (row, cols, func, decimal, unit) { let value; let arr1 = cols.map((v) => this.$formatNumber(row[v])); let arr = arr1.filter((v) => v); if (arr.length === 0) return { success: false, value: undefined }; if (func === "max") value = Math.max(...arr); else if (func === "min") value = Math.min(...arr); else if (func === "sum") value = arr.reduce((a, b) => a + b, 0); else if (func === "avg") { let total = arr.reduce((a, b) => a + b, 0); value = total / cols.length; } if (!value) return { success: false, value: undefined }; value = formatUnit(value, unit, decimal, true, decimal); return { success: true, value: value }; }; const calculateData = function (data, fields) { let arr = this.$copy(fields.filter((h) => h.formula)); if (arr.length === 0) return data; let arr1 = arr.filter((v) => v.func); arr1.map((v) => { if (v.vals.indexOf(":") >= 0) { let arr2 = v.vals.toLowerCase().replaceAll("c", "").split(":"); let cols = []; for (let i = parseInt(arr2[0]); i <= parseInt(arr2[1]); i++) { let field = fields.length > i ? fields[i] : undefined; if (field ? field.format === "number" && field.name !== v.name : false) cols.push(field.name); } v.cols = cols; } else { let arr2 = v.vals.toLowerCase().replaceAll("c", "").split(","); let cols = []; arr2.map((v) => { let i = parseInt(v); let field = fields.length > i ? fields[i] : undefined; if (field ? field.format === "number" && field.name !== v.name : false) cols.push(field.name); }); v.cols = cols; } }); arr = multiSort(arr, { level: "asc" }); let copy = data; copy.map((v) => { arr.map((x) => { if (x.func) { let res = calculateFunc(v, x.cols, x.func, x.decimal, x.unit); if (res ? res.success : false) v[x.name] = res.value; } else { let res = calculate(v, x.tags, x.formula, x.decimal, x.unit); if (res ? res.success : false) v[x.name] = res.value; } }); }); return copy; }; const summary = function (arr, fields, type) { let obj = {}; if (type === "total") { fields.map((x) => (obj[x] = arr.map((v) => (v[x] ? v[x] : 0)).reduce((a, b) => a + b, 0))); } else if (type === "min") { fields.map((x) => (obj[x] = Math.min(...arr.map((v) => v[x])))); } else if (type === "max") { fields.map((x) => (obj[x] = Math.max(...arr.map((v) => v[x])))); } else if (type === "count") { fields.map((x) => (obj[x] = arr.map((v) => !empty(v[x])).length)); } return obj; }; //====================Array==================== const formatArray = function (data, fields) { let args = fields.filter((v) => v.format === "number"); data.map((v) => { args.map((x) => { v[x.name] = empty(v[x.name]) ? undefined : formatUnit(v[x.name], x.unit, x.decimal, true, x.decimal); }); }); return data; }; const unique = function (arr, keyProps) { const kvArray = arr.map((entry) => { const key = keyProps.map((k) => entry[k]).join("|"); return [key, entry]; }); const map = new Map(kvArray); return Array.from(map.values()); }; const arrayMove = function (arr, old_index, new_index) { if (new_index >= arr.length) { var k = new_index - arr.length + 1; while (k--) { arr.push(undefined); } } arr.splice(new_index, 0, arr.splice(old_index, 1)[0]); return arr; // for testing }; const multiSort = function (array, sortObject = {}, format = {}) { const sortKeys = Object.keys(sortObject); // Return array if no sort object is supplied. if (!sortKeys.length) return array; // Change the values of the sortObject keys to -1, 0, or 1. for (let key in sortObject) sortObject[key] = sortObject[key] === "desc" || sortObject[key] === -1 ? -1 : sortObject[key] === "skip" || sortObject[key] === 0 ? 0 : 1; const keySort = (a, b, direction) => { direction = direction !== null ? direction : 1; if (a === b) return 0; // If b > a, multiply by -1 to get the reverse direction. return a > b ? direction : -1 * direction; }; return array.sort((a, b) => { let sorted = 0, index = 0; // Loop until sorted (-1 or 1) or until the sort keys have been processed. while (sorted === 0 && index < sortKeys.length) { const key = sortKeys[index]; if (key) { const direction = sortObject[key]; let val1 = format[key] === "number" ? (empty(a[key]) ? 0 : this.$formatNumber(a[key])) : a[key]; let val2 = format[key] === "number" ? (empty(b[key]) ? 0 : this.$formatNumber(b[key])) : b[key]; sorted = keySort(val1, val2, direction); index++; } } return sorted; }); }; //======================Fields==================== const createField = function (name, label, format, show, minwidth) { let field = { name: name, label: label, format: format, show: show, minwidth: minwidth, }; if (format === "number") { field.unit = "1"; field.textalign = "right"; } return field; }; const updateFields = function (pagename, field, action) { let pagedata = store[pagename]; let copy = this.$copy(pagedata.fields); let idx = this.$findIndex(copy, { name: field.name }); if (action === "delete") { this.$delete(copy, idx); if (pagedata.filters ? pagedata.filters.length > 0 : false) { let index = this.$findIndex(pagedata.filters, { name: field.name }); if (index >= 0) { let copyFilter = this.$copy(this.pagedata.filters); this.$delete(copyFilter, index); this.$store.commit("updateState", { name: pagename, key: "filterby", data: copyFilter, }); } } } else { idx >= 0 ? (copy[idx] = field) : copy.push(field); } this.$store.commit("updateState", { name: pagename, key: "fields", data: copy, }); }; const updateSeriesFields = function (fields) { if (!Array.isArray(fields)) { fields = Object.values(fields); } fields .filter((v) => v.series) .map((field) => { let obj = field.api === "finitem" ? this.$findPeriod(field.series, "period") : this.$findPeriod(field.series); field.period = field.api === "finitem" ? obj.code : obj; let idx = field.label.indexOf("["); field.label = (idx > 0 ? field.label.substring(0, idx) : field.label) + "[" + (field.api === "finitem" ? obj.show : obj) + "]"; }); return fields; }; const updateSeriesFilters = function (filters, fields) { if (fields.filter((v) => v.series).length === 0) return filters; filters.map((v) => { let found = fields.find((x) => x.name === v.name); if (found ? found.series : false) { v.label = found.label; } }); return filters; }; //======================Export=============================== const exportExcel = async function (data, filename, fields) { var _filename = filename + ".xlsx"; let list = []; data.map((v) => { let ele = {}; fields.map((x) => { let label = stripHtml(x.label); ele[label] = v[x.name]; }); list.push(ele); }); const XLSX = await import("xlsx"); // WorkBook class function Workbook() { if (!(this instanceof Workbook)) return new Workbook(); this.SheetNames = []; this.Sheets = {}; } var exportBook = new Workbook(); var worksheet = XLSX.utils.json_to_sheet(list); exportBook.SheetNames.push("sheet1"); exportBook.Sheets.sheet1 = worksheet; XLSX.writeFile(exportBook, _filename); }; return { provide: { store, find, findIndex, filter, id, empty, copy, clone, remove, stripHtml, isNumber, numtoString, formatNumber, formatUnit, calc, calculate, calculateFunc, calculateData, summary, formatArray, unique, arrayMove, multiSort, createField, updateFields, updateSeriesFields, updateSeriesFilters, exportExcel, }, }; });