(() => { function normalizeText(value) { return String(value || "").replace(/\s+/g, " ").trim(); } function cleanString(value) { return normalizeText(value); } function firstNonEmptyString(values) { for (const value of values) { const normalized = cleanString(value); if (normalized) { return normalized; } } return ""; } function normalizeString(value) { return normalizeText(value).toUpperCase(); } function toNumber(value) { if (typeof value === "number" && Number.isFinite(value)) { return value; } const numeric = Number.parseFloat(String(value ?? "").replace(/,/g, "")); return Number.isFinite(numeric) ? numeric : 0; } function toIsoDateString(value) { const normalized = cleanString(value); if (!normalized) { return ""; } const date = new Date(`${normalized}T12:00:00.000Z`); return Number.isNaN(date.getTime()) ? normalized : date.toISOString(); } function extractArray(value, candidateKeys = []) { if (Array.isArray(value)) { return value; } if (!value || typeof value !== "object") { return []; } for (const key of candidateKeys) { if (Array.isArray(value[key])) { return value[key]; } } for (const nestedValue of Object.values(value)) { if (Array.isArray(nestedValue)) { return nestedValue; } if (nestedValue && typeof nestedValue === "object") { const nestedArray = extractArray(nestedValue, candidateKeys); if (nestedArray.length > 0) { return nestedArray; } } } return []; } async function mapWithConcurrency(items, concurrency, mapper) { const results = new Array(items.length); let nextIndex = 0; async function worker() { while (nextIndex < items.length) { const currentIndex = nextIndex; nextIndex += 1; results[currentIndex] = await mapper(items[currentIndex], currentIndex); } } const workerCount = Math.min(concurrency, items.length || 1); await Promise.all(Array.from({ length: workerCount }, () => worker())); return results; } function getFieldValue(source, paths) { if (!source || typeof source !== "object") { return ""; } for (const path of paths) { if (Object.prototype.hasOwnProperty.call(source, path)) { return source[path]; } const parts = String(path).split("."); let current = source; for (const part of parts) { if (!current || typeof current !== "object") { current = ""; break; } current = current[part]; } if (current !== undefined && current !== null && current !== "") { return current; } } return ""; } function flattenDisplayRecord(value, prefix = "", output = []) { if (!value || typeof value !== "object" || Array.isArray(value)) { if (prefix) { output.push([prefix, value]); } return output; } for (const [key, nestedValue] of Object.entries(value)) { const nextKey = prefix ? `${prefix}.${key}` : key; if ( nestedValue && typeof nestedValue === "object" && !Array.isArray(nestedValue) ) { flattenDisplayRecord(nestedValue, nextKey, output); } else { output.push([nextKey, nestedValue]); } } return output; } function isVisible(element) { const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return ( style.display !== "none" && style.visibility !== "hidden" && Number.parseFloat(style.opacity || "1") > 0 && rect.width > 0 && rect.height > 0 ); } function formatWholeNumber(value) { return new Intl.NumberFormat("en-US", { maximumFractionDigits: 0, }).format(value); } function formatCurrency(value) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(value || 0); } function formatHours(value) { return new Intl.NumberFormat("pt-BR", { maximumFractionDigits: 2, }).format(toNumber(value)); } function formatWeekNumber(value) { return String(toNumber(value)).padStart(2, "0"); } function formatOperationalWeekLabel(weekInfo) { if (!weekInfo) { return ""; } return `FY${formatWeekNumber(weekInfo.fiscalYear % 100)}-Q${ weekInfo.quarter }-W${formatWeekNumber(weekInfo.weekNumber)}`; } function formatPercent(value) { return new Intl.NumberFormat("en-US", { maximumFractionDigits: 1, }).format(value || 0) + "%"; } function formatProgressPercent(value) { return new Intl.NumberFormat("en-US", { maximumFractionDigits: 0, }).format(value || 0) + "%"; } function formatDate(value) { if (!value) { return ""; } const date = parseDatePreservingDateOnly(value); if (Number.isNaN(date.getTime())) { return String(value); } return new Intl.DateTimeFormat("en-US", { dateStyle: "medium", }).format(date); } function getDateInputValue(value) { const date = parseDatePreservingDateOnly(value); if (Number.isNaN(date.getTime())) { return ""; } const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } function formatDateInputDisplay(value) { const date = parseDatePreservingDateOnly(value); if (Number.isNaN(date.getTime())) { return cleanString(value); } const day = String(date.getDate()).padStart(2, "0"); const month = String(date.getMonth() + 1).padStart(2, "0"); const year = date.getFullYear(); return `${day}/${month}/${year}`; } function formatDateForTimeEntryRequest(value) { const date = parseDatePreservingDateOnly(value); if (Number.isNaN(date.getTime())) { return ""; } const day = String(date.getDate()).padStart(2, "0"); const month = new Intl.DateTimeFormat("en-US", { month: "short" }) .format(date) .toUpperCase(); const year = date.getFullYear(); return `${day}-${month}-${year}`; } function parseTimeEntryDate(value) { const text = cleanString(value); const match = text.match(/^(\d{1,2})-([A-Z]{3})-(\d{4})$/i); if (!match) { return parseDatePreservingDateOnly(text); } const monthIndex = [ "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC", ].indexOf(match[2].toUpperCase()); if (monthIndex < 0) { return new Date(Number.NaN); } return new Date(Number(match[3]), monthIndex, Number(match[1])); } function formatDateTime(value) { if (!value) { return ""; } const date = new Date(value); if (Number.isNaN(date.getTime())) { return String(value); } return new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeStyle: "short", }).format(date); } function getLocalDateKey(value) { if (!value) { return ""; } const date = parseDatePreservingDateOnly(value); if (Number.isNaN(date.getTime())) { return ""; } const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } function getOperationalWeekInfo(value) { const date = parseDatePreservingDateOnly(value); if (Number.isNaN(date.getTime())) { return null; } const operationalYearStart = new Date(date.getFullYear(), 5, 1); if (date < operationalYearStart) { operationalYearStart.setFullYear(operationalYearStart.getFullYear() - 1); } const normalizedDate = new Date( date.getFullYear(), date.getMonth(), date.getDate() ); const normalizedStart = new Date( operationalYearStart.getFullYear(), operationalYearStart.getMonth(), operationalYearStart.getDate() ); const daysSinceStart = Math.floor( (normalizedDate.getTime() - normalizedStart.getTime()) / 86400000 ); const monthsSinceStart = (normalizedDate.getFullYear() - normalizedStart.getFullYear()) * 12 + normalizedDate.getMonth() - normalizedStart.getMonth(); return { fiscalYear: operationalYearStart.getFullYear() + 1, quarter: Math.floor(monthsSinceStart / 3) + 1, weekNumber: Math.floor(daysSinceStart / 7) + 1, }; } function getCalendarRowWeekDate(rowStartDate) { const weekDate = new Date(rowStartDate); if (weekDate.getDay() === 0) { weekDate.setDate(weekDate.getDate() + 1); } return weekDate; } function parseDatePreservingDateOnly(value) { if (value instanceof Date) { return value; } if (typeof value === "string") { const dateOnlyMatch = value.trim().match(/^(\d{4})-(\d{2})-(\d{2})$/); if (dateOnlyMatch) { return new Date( Number(dateOnlyMatch[1]), Number(dateOnlyMatch[2]) - 1, Number(dateOnlyMatch[3]) ); } } return new Date(value); } function decodeJwtPayload(token) { const normalizedToken = cleanString(token); if ( !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test( normalizedToken ) ) { return null; } try { const payloadPart = normalizedToken.split(".")[1]; const base64 = payloadPart.replace(/-/g, "+").replace(/_/g, "/"); const paddedBase64 = base64.padEnd(Math.ceil(base64.length / 4) * 4, "="); const json = decodeURIComponent( Array.from(atob(paddedBase64), (char) => { return `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`; }).join("") ); return JSON.parse(json); } catch { return null; } } function getJwtExpiresAt(token) { const payload = decodeJwtPayload(token); const expiresAtSeconds = Number(payload?.exp); return Number.isFinite(expiresAtSeconds) && expiresAtSeconds > 0 ? expiresAtSeconds * 1000 : 0; } function isJwtExpired(token, skewSeconds = 60) { const expiresAt = getJwtExpiresAt(token); if (!expiresAt) { return false; } const skewMilliseconds = Math.max(Number(skewSeconds) || 0, 0) * 1000; return expiresAt <= Date.now() + skewMilliseconds; } function getErrorMessage(error) { if (error instanceof Error) { return error.message; } return "Unexpected error while fetching live data."; } function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function cssEscape(value) { if (window.CSS?.escape) { return window.CSS.escape(String(value || "")); } return String(value || "").replace(/["\\]/g, "\\$&"); } globalThis.ArchPanelUtils = Object.freeze({ normalizeText, cleanString, firstNonEmptyString, normalizeString, toNumber, toIsoDateString, extractArray, mapWithConcurrency, getFieldValue, flattenDisplayRecord, isVisible, formatWholeNumber, formatCurrency, formatHours, formatWeekNumber, formatOperationalWeekLabel, formatPercent, formatProgressPercent, formatDate, getDateInputValue, formatDateInputDisplay, formatDateForTimeEntryRequest, parseTimeEntryDate, formatDateTime, getLocalDateKey, getOperationalWeekInfo, getCalendarRowWeekDate, parseDatePreservingDateOnly, decodeJwtPayload, getJwtExpiresAt, isJwtExpired, getErrorMessage, escapeHtml, cssEscape, }); })();