151 lines
4.6 KiB
JavaScript
151 lines
4.6 KiB
JavaScript
/*
|
|
* Utility functions used across multiple frontend modules. These are pure
|
|
* functions with no side effects and no dependency on application state.
|
|
*/
|
|
|
|
/* ── Formatting ─────────────────────────────────────────────────────────── */
|
|
|
|
export function prettifyStatus(status) {
|
|
return status
|
|
.split('_')
|
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
.join(' ');
|
|
}
|
|
|
|
export function formatDateTime(value) {
|
|
return new Date(value).toLocaleString();
|
|
}
|
|
|
|
export function formatTime(value) {
|
|
return new Date(value).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
|
|
export function formatRelativeTime(value) {
|
|
const diffMs = Date.now() - new Date(value).getTime();
|
|
const diffMinutes = Math.round(diffMs / 60000);
|
|
|
|
if (diffMinutes < 1) {
|
|
return 'just now';
|
|
}
|
|
|
|
if (diffMinutes < 60) {
|
|
return `${diffMinutes} minute(s) ago`;
|
|
}
|
|
|
|
const diffHours = Math.round(diffMinutes / 60);
|
|
|
|
if (diffHours < 24) {
|
|
return `${diffHours} hour(s) ago`;
|
|
}
|
|
|
|
return `${Math.round(diffHours / 24)} day(s) ago`;
|
|
}
|
|
|
|
export function formatFileSize(bytes) {
|
|
if (bytes < 1024) {
|
|
return `${bytes} B`;
|
|
}
|
|
|
|
if (bytes < 1024 * 1024) {
|
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
}
|
|
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
/* ── Naming & Sanitization ──────────────────────────────────────────────── */
|
|
|
|
export function sanitizeForFilename(value) {
|
|
return String(value || 'report')
|
|
.trim()
|
|
.replace(/\s+/g, '-')
|
|
.replace(/[^a-zA-Z0-9-_]/g, '')
|
|
.slice(0, 40);
|
|
}
|
|
|
|
export function generateReportNumber() {
|
|
const now = new Date();
|
|
const stamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(
|
|
now.getDate()
|
|
).padStart(2, '0')}-${String(now.getHours()).padStart(2, '0')}${String(
|
|
now.getMinutes()
|
|
).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
|
|
|
|
return `POC-${stamp}`;
|
|
}
|
|
|
|
export function buildGeneratedFilename(report, field, sequence, extension) {
|
|
const reportNumber = sanitizeForFilename(report.answers.reportNumber || report.reportNumber);
|
|
const sectionCode = sanitizeForFilename(field.id).slice(0, 10).toUpperCase();
|
|
return `${reportNumber}_${sectionCode}_${String(sequence).padStart(3, '0')}.${extension}`;
|
|
}
|
|
|
|
/* ── Badge helpers ──────────────────────────────────────────────────────── */
|
|
|
|
export function badgeClassForTone(tone) {
|
|
if (tone === 'success') {
|
|
return 'badge-online';
|
|
}
|
|
|
|
if (tone === 'error') {
|
|
return 'badge-error';
|
|
}
|
|
|
|
if (tone === 'warning') {
|
|
return 'badge-offline';
|
|
}
|
|
|
|
return 'badge-neutral';
|
|
}
|
|
|
|
/* ── Template helpers ───────────────────────────────────────────────────── */
|
|
|
|
export function makeTemplateKey(code, version) {
|
|
return `${code}::${version}`;
|
|
}
|
|
|
|
/*
|
|
* Multiple versions of the same template can exist in local cache because old
|
|
* drafts remain bound to the version they started with. The catalog therefore
|
|
* picks the newest version per template code for the creation UI while keeping
|
|
* older records available in the version lookup map.
|
|
*/
|
|
export function deriveTemplateCatalog(templateRows) {
|
|
const byCode = new Map();
|
|
|
|
for (const row of templateRows) {
|
|
const existing = byCode.get(row.code);
|
|
const shouldReplace = !existing || Number(row.version) > Number(existing.version);
|
|
|
|
if (shouldReplace) {
|
|
byCode.set(row.code, row);
|
|
}
|
|
}
|
|
|
|
return Array.from(byCode.values())
|
|
.sort((left, right) => left.name.localeCompare(right.name))
|
|
.map((item) => ({
|
|
code: item.code,
|
|
name: item.name,
|
|
description: item.description,
|
|
activeVersion: item.version,
|
|
publishedAt: item.publishedAt
|
|
}));
|
|
}
|
|
|
|
/* ── General ────────────────────────────────────────────────────────────── */
|
|
|
|
/*
|
|
* Debounce helper. Returns a wrapper that delays invocation until `ms`
|
|
* milliseconds of inactivity have passed, preventing expensive operations
|
|
* (such as a full form re-render) from running on every keystroke.
|
|
*/
|
|
export function debounce(fn, ms) {
|
|
let timer = null;
|
|
|
|
return function debounced(...args) {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => fn.apply(this, args), ms);
|
|
};
|
|
}
|