/** Общие мелочи для админ-разделов: бейджи статусов, формат времени, сокращение. */
import { h } from "vue";
import { GnTooltip } from "gnexus-ui-kit/vue";
/** Разрешённые варианты кита: primary, secondary, accent, success, warning, danger, error, info. */
export const statusVariant = {
// delivery
delivered: "success",
pending: "info",
failed: "danger",
skipped: "warning",
// event
queued: "info",
processing: "info",
done: "success",
};
export function badgeFor(status) {
return { label: status, variant: statusVariant[status] || "secondary" };
}
/** Вариант бейджа для канала: служебный internal_log нейтрален. */
export function badgeChannel(channel) {
return channel === "internal_log" ? "secondary" : "accent";
}
// Время первым, как просил владелец: «03:44:35, 3 октября» (год — только не текущий).
const MONTHS = [
"января", "февраля", "марта", "апреля", "мая", "июня",
"июля", "августа", "сентября", "октября", "ноября", "декабря",
];
export function fmtTime(iso) {
if (!iso) return "—";
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return String(iso);
const pad = (n) => String(n).padStart(2, "0");
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
const date = `${d.getDate()} ${MONTHS[d.getMonth()]}`;
const year = d.getFullYear() === new Date().getFullYear() ? "" : ` ${d.getFullYear()}`;
return `${time}, ${date}${year}`;
}
const TOKEN_RE = /("(?:[^"\\]|\\.)*")(\s*:)?|(-?\d+(?:\.\d+)?)\b|(\btrue\b|\bfalse\b|\bnull\b)/g;
/**
* Подсветка JSON: массив сегментов {cls, text} для CodeBlock — ключи, строки,
* числа, булевы/null окрашиваются классами; пунктуация остаётся базовой.
*/
export function jsonSpans(value) {
const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
const spans = [];
let last = 0;
for (const m of text.matchAll(TOKEN_RE)) {
const [full, str, colon, num, bool] = m;
const start = m.index;
if (start > last) spans.push({ cls: "", text: text.slice(last, start) });
if (str !== undefined) {
// строка с двоеточием после неё — ключ объекта
spans.push({ cls: colon ? "code-key" : "code-string", text: full });
} else if (num !== undefined) {
spans.push({ cls: "code-number", text: num });
} else if (bool !== undefined) {
spans.push({ cls: "code-boolean", text: bool });
}
last = start + full.length;
}
if (last < text.length) spans.push({ cls: "", text: text.slice(last) });
return spans;
}
/**
* Заголовок колонки GnTable с тултипом кита: GnTable рендерит column.label
* как есть, поэтому передаём VNode с GnTooltip вокруг подписи.
*/
export function th(label, text) {
return h(GnTooltip, { text }, () => label);
}
export function truncate(text, n = 80) {
if (!text) return "";
return text.length > n ? `${text.slice(0, n - 1)}…` : text;
}
/** JSON в одну строку для ячейки таблицы. */
export function shortJson(value, n = 60) {
if (!value || (typeof value === "object" && !Object.keys(value).length)) return "—";
const text = JSON.stringify(value);
return truncate(text, n);
}
/** Разбор строки с запятыми/пробелами в массив непустых токенов. */
export function parseList(text) {
return (text || "")
.split(/[,\n]+/)
.map((s) => s.trim())
.filter(Boolean);
}