General source code refactoring
This commit is contained in:
155
src/features/consumption/model.js
Normal file
155
src/features/consumption/model.js
Normal file
@@ -0,0 +1,155 @@
|
||||
function createConsumptionModel() {
|
||||
function normalizeConsumptionTrendItems(items) {
|
||||
const points = new Map();
|
||||
|
||||
(Array.isArray(items) ? items : []).forEach((item) => {
|
||||
const dateKey = String(item.fyDate || (Array.isArray(item.fiscalTime) ? item.fiscalTime.join("|") : ""));
|
||||
|
||||
if (!dateKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!points.has(dateKey)) {
|
||||
points.set(dateKey, {
|
||||
dateKey,
|
||||
date: parseConsumptionDate(item.fyDate),
|
||||
fiscalTime: Array.isArray(item.fiscalTime) ? item.fiscalTime.slice() : [],
|
||||
fiscalQtr: item.fiscalQtr || "",
|
||||
funded: 0,
|
||||
overage: 0,
|
||||
arr: 0,
|
||||
currencyCode: item.currencyCode || "USD"
|
||||
});
|
||||
}
|
||||
|
||||
const point = points.get(dateKey);
|
||||
const amount = toFiniteNumber(item.usedAmount);
|
||||
const category = String(item.usageCategory || "Funded Allocation").toUpperCase();
|
||||
|
||||
if (category === "OVERAGE") {
|
||||
point.overage += amount;
|
||||
} else {
|
||||
point.funded += amount;
|
||||
}
|
||||
|
||||
point.arr = Math.max(point.arr, toFiniteNumber(item.arrCd));
|
||||
});
|
||||
|
||||
return Array.from(points.values())
|
||||
.map((point) => ({ ...point, total: point.funded + point.overage }))
|
||||
.sort((left, right) => left.dateKey.localeCompare(right.dateKey));
|
||||
}
|
||||
|
||||
function getConsumptionPointTotal(point, activeSeries) {
|
||||
const selectedSeries = activeSeries && typeof activeSeries.has === "function"
|
||||
? activeSeries
|
||||
: new Set(["funded", "overage"]);
|
||||
return (selectedSeries.has("funded") ? point.funded : 0)
|
||||
+ (selectedSeries.has("overage") ? point.overage : 0);
|
||||
}
|
||||
|
||||
function calculateAverageConsumption(points, hasPartialLatestPoint, activeSeries) {
|
||||
const completePoints = hasPartialLatestPoint && points.length > 1 ? points.slice(0, -1) : points;
|
||||
return completePoints.reduce(
|
||||
(sum, point) => sum + getConsumptionPointTotal(point, activeSeries),
|
||||
0
|
||||
) / Math.max(completePoints.length, 1);
|
||||
}
|
||||
|
||||
function createNiceConsumptionMaximum(value) {
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const magnitude = 10 ** Math.floor(Math.log10(value));
|
||||
const normalized = value / magnitude;
|
||||
const step = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
|
||||
return step * magnitude;
|
||||
}
|
||||
|
||||
function formatConsumptionPointLabel(point, period) {
|
||||
if (period === "quarterly") {
|
||||
return point.fiscalTime[0] || point.fiscalQtr || point.dateKey;
|
||||
}
|
||||
|
||||
if (period === "monthly") {
|
||||
return point.fiscalTime[1] || formatConsumptionDate(point.date, { month: "short" });
|
||||
}
|
||||
|
||||
return point.fiscalTime[0] || formatConsumptionDate(point.date, {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "2-digit"
|
||||
});
|
||||
}
|
||||
|
||||
function formatConsumptionRange(points, period) {
|
||||
if (points.length === 0) {
|
||||
return "No data";
|
||||
}
|
||||
|
||||
if (period === "quarterly") {
|
||||
return `${formatConsumptionPointLabel(points[0], period)} - ${formatConsumptionPointLabel(points[points.length - 1], period)}`;
|
||||
}
|
||||
|
||||
if (period === "monthly") {
|
||||
const options = { month: "short", year: "numeric" };
|
||||
return `${formatConsumptionDate(points[0].date, options)} - ${formatConsumptionDate(points[points.length - 1].date, options)}`;
|
||||
}
|
||||
|
||||
const options = { day: "2-digit", month: "short" };
|
||||
return `${formatConsumptionDate(points[0].date, options)} - ${formatConsumptionDate(points[points.length - 1].date, options)}`;
|
||||
}
|
||||
|
||||
function parseConsumptionDate(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parts = String(value).split("-").map(Number);
|
||||
return parts.length === 3 && parts.every(Number.isFinite)
|
||||
? new Date(parts[0], parts[1] - 1, parts[2])
|
||||
: null;
|
||||
}
|
||||
|
||||
function formatConsumptionDate(date, options) {
|
||||
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat("en-US", options).format(date).replace(",", "");
|
||||
}
|
||||
|
||||
function formatCompactConsumptionNumber(value) {
|
||||
const amount = toFiniteNumber(value);
|
||||
|
||||
if (Math.abs(amount) >= 1000000) {
|
||||
return `${(amount / 1000000).toFixed(amount >= 10000000 ? 0 : 1).replace(".0", "")}M`;
|
||||
}
|
||||
|
||||
if (Math.abs(amount) >= 1000) {
|
||||
return `${Math.round(amount / 1000)}K`;
|
||||
}
|
||||
|
||||
return String(Math.round(amount));
|
||||
}
|
||||
|
||||
function formatConsumptionCurrency(value, currencyCode) {
|
||||
try {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: currencyCode || "USD",
|
||||
maximumFractionDigits: 0
|
||||
}).format(toFiniteNumber(value));
|
||||
} catch (error) {
|
||||
return `${currencyCode || "USD"} ${Math.round(toFiniteNumber(value)).toLocaleString("en-US")}`;
|
||||
}
|
||||
}
|
||||
|
||||
function toFiniteNumber(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : 0;
|
||||
}
|
||||
|
||||
return { normalizeConsumptionTrendItems, getConsumptionPointTotal, calculateAverageConsumption, createNiceConsumptionMaximum, formatConsumptionPointLabel, formatConsumptionRange, parseConsumptionDate, formatConsumptionDate, formatCompactConsumptionNumber, formatConsumptionCurrency, toFiniteNumber };
|
||||
}
|
||||
Reference in New Issue
Block a user