Reestruturação dos arquivos e pastas do código da extensão
This commit is contained in:
463
src/shared/utils.js
Normal file
463
src/shared/utils.js
Normal file
@@ -0,0 +1,463 @@
|
||||
(() => {
|
||||
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 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, """)
|
||||
.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,
|
||||
getErrorMessage,
|
||||
escapeHtml,
|
||||
cssEscape,
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user