Files
web/app/plugins/00-datatable.js
2026-05-06 09:34:22 +07:00

455 lines
14 KiB
JavaScript

import { useStore } from "~/stores/index";
export default defineNuxtPlugin(() => {
const store = useStore();
//==========Find & filter=================
const find = function (arr, obj, attr) {
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);
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 copy = function (val) {
if (empty(val)) return val;
return JSON.parse(JSON.stringify(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 = 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);
});
var XLSX = require("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,
},
};
});