/**
* Helper compatibility object.
*
* Mirrors the public surface of gnexus-ui-kit/src/js/components/helper.js:
* template, unification, states
*/
function sidebarNav(items) {
let listItems = "";
for (const item of items) {
let aOpen = "";
let aClose = "";
if (item.route) {
aOpen = `<a class="list-action" href="${item.route}">`;
aClose = "</a>";
}
listItems += `
<li class="list-item ${item.is_active ? "list-item-active" : ""}">
${aOpen}${item.content}${aClose}
</li>
`;
}
return `
<div class="sidebar block">
<ul class="list list-nav">
${listItems}
</ul>
</div>
`;
}
function table(caption, columns, data, tfoot) {
let head = `<tr class="table-row">`;
let totalColumns = 0;
for (const key in columns) {
head += `<th scope="col">${columns[key]}</th>`;
totalColumns++;
}
head += "</tr>";
let body = "";
for (const item of data) {
body += `<tr class="table-row">`;
for (const column in columns) {
body += `<td>${item[column]}</td>`;
}
body += `</tr>`;
}
let foot = "";
if (typeof tfoot !== "undefined") {
foot = `
<tfoot class="table-foot">
<tr class="table-row">
<td colspan="${totalColumns}">
${tfoot}
</td>
</tr>
</tfoot>
`;
}
const tableCaption = caption ? `<caption class="table-caption">${caption}</caption>` : "";
const tableHead = data.length ? `<thead class="table-head">${head}</thead>` : "";
body = data.length ? body : `<tr><td class="is-empty">Empty</td></tr>`;
const tableEmptyClass = !data.length ? "table-empty" : "";
return `
<div class="table-wrapper">
<table class="table data-list ${tableEmptyClass}">
${tableCaption}
${tableHead}
<tbody class="table-body">${body}</tbody>
${foot}
</table>
</div>
`;
}
function createElement(type, props, content) {
const node = document.createElement(type);
for (const [key, value] of Object.entries(props || {})) {
if (key === "class") {
node.className = value;
} else if (key === "dataset") {
Object.assign(node.dataset, value);
} else {
node.setAttribute(key, value);
}
}
node.innerHTML = typeof content !== "undefined" ? content : "";
return node;
}
function createAlert(type, content) {
const normalizedType = type === "error" ? "danger" : type;
if (["primary", "secondary", "accent", "success", "info", "warning", "danger"].indexOf(normalizedType) < 0) {
return console.error("createAlert()", "Error of type: " + type);
}
return createElement("div", { class: `alert alert-${normalizedType}` }, content);
}
function fieldsUnification(data, map = {}) {
const dataObj = {};
for (const field in data) {
if (typeof map[field] !== "undefined") {
dataObj[map[field]] = data[field];
continue;
}
dataObj[field] = data[field];
}
return dataObj;
}
function btnLoadingState(btn, isLoading) {
if (btn?.isLoading === isLoading) {
return false;
}
if (isLoading) {
btn.isLoading = true;
btn.originalContent = btn.innerHTML;
if (btn.classList.contains("with-icon")) {
btn.originalWithIcon = true;
} else {
btn.classList.add("with-icon");
}
btn.classList.add("loading-state");
btn.setAttribute("disabled", "disabled");
btn.innerHTML = `<i class="ph ph-bold ph-spinner"></i> Loading`;
} else {
btn.isLoading = false;
if (!btn.originalContent) {
return false;
}
btn.removeAttribute("disabled");
btn.classList.remove("loading-state");
if (!btn.originalWithIcon) {
btn.classList.remove("with-icon");
}
btn.innerHTML = btn.originalContent;
}
return btn;
}
function cardStatusLoadingState(card, isLoading) {
if (card?.isLoading === isLoading) {
return false;
}
const iconContainer = card.querySelector(".status-icon");
if (!iconContainer) {
return false;
}
if (isLoading) {
card.isLoading = true;
card.originalContent = iconContainer.innerHTML;
card.classList.add("loading-state");
card.setAttribute("disabled", "disabled");
iconContainer.innerHTML = `<i class="ph ph-bold ph-spinner"></i>`;
} else {
card.isLoading = false;
if (!card.originalContent) {
return false;
}
card.removeAttribute("disabled");
card.classList.remove("loading-state");
iconContainer.innerHTML = card.originalContent;
}
return card;
}
function mainTemplate(sidebar, content) {
content = content ?? "";
return `
<div class="container">
<div class="row adaptive g-6">
<div class="col sidebar-container">
${sidebar}
</div>
<div class="col main-container w-100">
${content}
</div>
</div>
</div>
`;
}
function connectionStatusBadge(status) {
return status === "active"
? `<span class="badge badge-success">Online</span>`
: `<span class="badge badge-warning">Offline</span>`;
}
function toogleStateBadge(state) {
return state === "enabled"
? `<span class="badge badge-success">Enabled</span>`
: `<span class="badge badge-primary">Disabled</span>`;
}
function timeAgo(dateString) {
const date = new Date(dateString.replace(" ", "T"));
const now = new Date();
const diffSeconds = Math.floor((now - date) / 1000);
if (diffSeconds < 60) {
return "less than a minute ago";
}
const minutes = Math.floor(diffSeconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (minutes < 60) {
return `${minutes} minute${minutes !== 1 ? "s" : ""} ago`;
}
if (hours < 24) {
const remainMinutes = minutes % 60;
return `${hours} hour${hours !== 1 ? "s" : ""} ${remainMinutes} minute${remainMinutes !== 1 ? "s" : ""} ago`;
}
return `${days} day${days !== 1 ? "s" : ""} ago`;
}
function formatDate(dateString) {
const date = new Date(dateString.replace(" ", "T"));
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const targetDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const diffDays = Math.floor((today - targetDay) / 86400000);
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
if (diffDays === 0) {
return `Today at ${hours}:${minutes}`;
}
if (diffDays === 1) {
return `Yesterday at ${hours}:${minutes}`;
}
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()} at ${hours}:${minutes}`;
}
function circleLoaderHTML() {
return `
<div class="circle-loader">
<i class="ph ph-bold ph-spinner normalize"></i>
Loading
</div>
`;
}
function emptyHereHTML() {
return `
<div class="empty-here">
<div class="icon"><i class="ph ph-placeholder normalize"></i></div>
<p class="text-msg">It's empty here yet</p>
</div>
`;
}
export default {
template: {
sidebarNav,
table,
createElement,
createAlert,
mainTemplate,
connectionStatusBadge,
toogleStateBadge,
circleLoaderHTML,
emptyHereHTML
},
unification: {
fieldsUnification,
timeAgo,
formatDate
},
states: {
btnLoadingState,
cardStatusLoadingState
}
};