bugfixing

This commit is contained in:
MuslemRahimi 2024-12-03 18:20:36 +01:00
parent 681d9cc423
commit 80e83b9e14
6 changed files with 465 additions and 165 deletions

11
package-lock.json generated
View File

@ -75,7 +75,7 @@
"svelte-intersection-observer": "^1.0.0", "svelte-intersection-observer": "^1.0.0",
"svelte-intersection-observer-action": "^0.0.5", "svelte-intersection-observer-action": "^0.0.5",
"svelte-inview": "^4.0.2", "svelte-inview": "^4.0.2",
"svelte-lazy": "^1.2.7", "svelte-lazy": "^1.2.11",
"svelte-lightweight-charts": "^2.2.0", "svelte-lightweight-charts": "^2.2.0",
"svelte-loading-spinners": "^0.3.6", "svelte-loading-spinners": "^0.3.6",
"svelte-preprocess": "^5.1.4", "svelte-preprocess": "^5.1.4",
@ -8205,12 +8205,13 @@
} }
}, },
"node_modules/svelte-lazy": { "node_modules/svelte-lazy": {
"version": "1.2.7", "version": "1.2.11",
"resolved": "https://registry.npmjs.org/svelte-lazy/-/svelte-lazy-1.2.7.tgz", "resolved": "https://registry.npmjs.org/svelte-lazy/-/svelte-lazy-1.2.11.tgz",
"integrity": "sha512-TvOP22pNsSKlCRsQIZ9mv1IAaEhSZFhezzhMO/kYuKUvX9VQALJvWHV5MOymn/pjeTeVuT4lE9g9y40uf0bNSw==", "integrity": "sha512-VVrkn5eMLgAHSZlZrApR675MqQACqlA1pWEI7rDi9kQ3GTsyXEaMjwFpzddnNsNWV4B0Tvd1pZHplQgFiBqPfg==",
"dev": true, "dev": true,
"license": "UNLICENSE",
"peerDependencies": { "peerDependencies": {
"svelte": "^3.0.0 || ^4.0.0" "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
} }
}, },
"node_modules/svelte-lightweight-charts": { "node_modules/svelte-lightweight-charts": {

View File

@ -74,7 +74,7 @@
"svelte-intersection-observer": "^1.0.0", "svelte-intersection-observer": "^1.0.0",
"svelte-intersection-observer-action": "^0.0.5", "svelte-intersection-observer-action": "^0.0.5",
"svelte-inview": "^4.0.2", "svelte-inview": "^4.0.2",
"svelte-lazy": "^1.2.7", "svelte-lazy": "^1.2.11",
"svelte-lightweight-charts": "^2.2.0", "svelte-lightweight-charts": "^2.2.0",
"svelte-loading-spinners": "^0.3.6", "svelte-loading-spinners": "^0.3.6",
"svelte-preprocess": "^5.1.4", "svelte-preprocess": "^5.1.4",

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { analystEstimateComponent, stockTicker } from "$lib/store"; import { analystEstimateComponent, stockTicker } from "$lib/store";
import { abbreviateNumber } from "$lib/utils"; import { abbreviateNumber, computeGrowthSingleList } from "$lib/utils";
import EstimationGraph from "$lib/components/EstimationGraph.svelte"; import EstimationGraph from "$lib/components/EstimationGraph.svelte";
import Lazy from "svelte-lazy"; import Lazy from "svelte-lazy";
@ -12,19 +12,29 @@
let xData = []; let xData = [];
let optionsRevenue = null; let optionsRevenue = null;
let optionsEPS = null; let optionsEPS = null;
let optionsNetIncome = null;
let optionsRevenueGrowth = null; let optionsRevenueGrowth = null;
let optionsEPSGrowth = null; let optionsEPSGrowth = null;
let optionsNetIncomeGrowth = null;
let revenueDateList = []; let revenueDateList = [];
let avgRevenueList = []; let avgRevenueList = [];
let lowRevenueList = []; let lowRevenueList = [];
let highRevenueList = []; let highRevenueList = [];
let epsDateList = []; let epsDateList = [];
let avgEPSList = []; let avgEPSList = [];
let lowEPSList = []; let lowEPSList = [];
let highEPSList = []; let highEPSList = [];
let netIncomeDateList = [];
let avgNetIncomeList = [];
let lowNetIncomeList = [];
let highNetIncomeList = [];
let revenueAvgGrowthList = []; let revenueAvgGrowthList = [];
let epsAvgGrowthList = []; let epsAvgGrowthList = [];
let netIncomeAvgGrowthList = [];
let displayData = "Revenue"; let displayData = "Revenue";
@ -56,42 +66,6 @@
return data; return data;
}); });
} }
function computeGrowthSingleList(data, actualList) {
// Initialize the result list
let resultList = [];
for (let i = 0; i < data?.length; i++) {
const currentData = data[i];
// Find the corresponding actual data from one FY back
const correspondingActual = actualList?.find(
(entry) => Number(entry.FY) === Number(currentData.FY) - 1,
);
// Calculate growth if a matching entry exists in actualList
let growth = null;
if (
correspondingActual &&
correspondingActual?.val !== null &&
currentData.val !== null
) {
growth = (
((currentData?.val - correspondingActual?.val) /
Math.abs(correspondingActual?.val)) *
100
)?.toFixed(2);
}
// Push the result for this FY
resultList.push({
FY: currentData.FY,
val: currentData.val,
growth: growth !== null ? Number(growth) : null, // Convert growth to number or leave as null
});
}
return resultList;
}
function computeGrowthList(tableActualRevenue, tableForecastRevenue) { function computeGrowthList(tableActualRevenue, tableForecastRevenue) {
return tableActualRevenue?.map((item, index) => { return tableActualRevenue?.map((item, index) => {
@ -162,9 +136,14 @@
let tableForecastRevenue = []; let tableForecastRevenue = [];
let tableForecastEPS = []; let tableForecastEPS = [];
let tableForecastNetIncome = [];
let tableCombinedRevenue = []; let tableCombinedRevenue = [];
let tableCombinedEPS = []; let tableCombinedEPS = [];
let tableActualNetIncome = [];
let tableCombinedNetIncome = [];
function getPlotOptions(dataType: string) { function getPlotOptions(dataType: string) {
let dates = []; let dates = [];
let valueList = []; let valueList = [];
@ -182,17 +161,25 @@
dates.push(`FY${date}`); dates.push(`FY${date}`);
switch (dataType) { switch (dataType) {
case "Revenue": case "Revenue":
valueList.push(item.revenue); valueList.push(item?.revenue);
avgList.push(isAfterStartIndex ? item.estimatedRevenueAvg : null); avgList.push(isAfterStartIndex ? item.estimatedRevenueAvg : null);
lowList.push(isAfterStartIndex ? item.estimatedRevenueLow : null); lowList.push(isAfterStartIndex ? item.estimatedRevenueLow : null);
highList.push(isAfterStartIndex ? item.estimatedRevenueHigh : null); highList.push(isAfterStartIndex ? item.estimatedRevenueHigh : null);
break; break;
case "EPS": case "EPS":
valueList.push(item.eps); valueList.push(item?.eps);
avgList.push(isAfterStartIndex ? item.estimatedEpsAvg : null); avgList.push(isAfterStartIndex ? item.estimatedEpsAvg : null);
lowList.push(isAfterStartIndex ? item.estimatedEpsLow : null); lowList.push(isAfterStartIndex ? item.estimatedEpsLow : null);
highList.push(isAfterStartIndex ? item.estimatedEpsHigh : null); highList.push(isAfterStartIndex ? item.estimatedEpsHigh : null);
break; break;
case "NetIncome":
valueList.push(item?.netIncome);
avgList.push(isAfterStartIndex ? item.estimatedNetIncomeAvg : null);
lowList.push(isAfterStartIndex ? item.estimatedNetIncomeLow : null);
highList.push(
isAfterStartIndex ? item.estimatedNetIncomeHigh : null,
);
break;
default: default:
break; break;
} }
@ -249,12 +236,33 @@
FY: epsDateList[index]?.slice(2), FY: epsDateList[index]?.slice(2),
val: val, val: val,
})) || []; })) || [];
} else if (dataType === "NetIncome") {
netIncomeDateList = dates?.slice(currentYearIndex) || [];
avgNetIncomeList =
avgList?.slice(currentYearIndex)?.map((val, index) => ({
FY: netIncomeDateList[index]?.slice(2),
val: val,
})) || [];
lowNetIncomeList =
lowList?.slice(currentYearIndex)?.map((val, index) => ({
FY: netIncomeDateList[index]?.slice(2),
val: val,
})) || [];
highNetIncomeList =
highList?.slice(currentYearIndex)?.map((val, index) => ({
FY: netIncomeDateList[index]?.slice(2),
val: val,
})) || [];
} }
const growthList = dates?.map((date) => { const growthList = dates?.map((date) => {
const fy = parseInt(date.replace("FY", ""), 10); // Extract numeric FY value const fy = parseInt(date.replace("FY", ""), 10); // Extract numeric FY value
const listToUse = const listToUse =
dataType === "Revenue" ? revenueAvgGrowthList : epsAvgGrowthList; // Select the correct growth list dataType === "Revenue"
? revenueAvgGrowthList
: dataType === "EPS"
? epsAvgGrowthList
: netIncomeAvgGrowthList; // Select the correct growth list
const growth = listToUse?.find((r) => r.FY === fy); // Find matching FY const growth = listToUse?.find((r) => r.FY === fy); // Find matching FY
return growth ? growth?.growth : null; // Return growth or null if not found return growth ? growth?.growth : null; // Return growth or null if not found
}); });
@ -350,10 +358,20 @@
if (dataType === "Revenue") { if (dataType === "Revenue") {
highGrowthList = computeGrowthSingleList(highRevenueList, avgRevenueList); highGrowthList = computeGrowthSingleList(highRevenueList, avgRevenueList);
lowGrowthList = computeGrowthSingleList(lowRevenueList, avgRevenueList); lowGrowthList = computeGrowthSingleList(lowRevenueList, avgRevenueList);
} else { } else if (dataType === "EPS") {
highGrowthList = computeGrowthSingleList(highEPSList, avgEPSList); highGrowthList = computeGrowthSingleList(highEPSList, avgEPSList);
lowGrowthList = computeGrowthSingleList(lowEPSList, avgEPSList); lowGrowthList = computeGrowthSingleList(lowEPSList, avgEPSList);
} else if (dataType === "NetIncome") {
highGrowthList = computeGrowthSingleList(
highNetIncomeList,
avgNetIncomeList,
);
lowGrowthList = computeGrowthSingleList(
lowNetIncomeList,
avgNetIncomeList,
);
} }
highGrowthList = fillMissingDates(dates, highGrowthList)?.map( highGrowthList = fillMissingDates(dates, highGrowthList)?.map(
(item) => item?.growth, (item) => item?.growth,
); );
@ -392,7 +410,12 @@
], ],
series: [ series: [
{ {
name: dataType === "Revenue" ? "Revenue Growth" : "EPS Growth", name:
dataType === "Revenue"
? "Revenue Growth"
: dataType === "EPS"
? "EPS Growth"
: "Net Income Growth",
data: growthList?.map((value) => ({ data: growthList?.map((value) => ({
value, value,
itemStyle: { itemStyle: {
@ -518,6 +541,9 @@
} else if (dataType === "EPS") { } else if (dataType === "EPS") {
optionsEPS = option; optionsEPS = option;
optionsEPSGrowth = optionsGrowth; optionsEPSGrowth = optionsGrowth;
} else if (dataType === "NetIncome") {
optionsNetIncome = option;
optionsNetIncomeGrowth = optionsGrowth;
} }
} }
@ -526,18 +552,24 @@
tableActualRevenue = []; tableActualRevenue = [];
tableForecastRevenue = []; tableForecastRevenue = [];
tableCombinedRevenue = []; tableCombinedRevenue = [];
tableCombinedEPS = [];
tableActualEPS = []; tableActualEPS = [];
tableCombinedEPS = [];
tableForecastEPS = []; tableForecastEPS = [];
tableActualNetIncome = [];
tableCombinedNetIncome = [];
tableForecastNetIncome = [];
revenueAvgGrowthList = []; revenueAvgGrowthList = [];
epsAvgGrowthList = []; epsAvgGrowthList = [];
netIncomeAvgGrowthList = [];
let filteredData = let filteredData =
analystEstimateList?.filter((item) => item.date >= 2015) ?? []; analystEstimateList?.filter((item) => item.date >= 2015) ?? [];
xData = filteredData?.map(({ date }) => Number(String(date)?.slice(-2))); xData = filteredData?.map(({ date }) => Number(String(date)?.slice(-2)));
//============================//
//Revenue Data //Revenue Data
filteredData?.forEach((item) => { filteredData?.forEach((item) => {
tableActualRevenue?.push({ tableActualRevenue?.push({
@ -565,6 +597,33 @@
}; };
}); });
//============================//
//NetIncome Data
filteredData?.forEach((item) => {
tableActualNetIncome?.push({
FY: Number(String(item?.date)?.slice(-2)),
val: item?.netIncome,
});
tableForecastNetIncome?.push({
FY: Number(String(item?.date)?.slice(-2)),
val: item?.estimatedNetIncomeAvg,
});
});
tableCombinedNetIncome = tableActualNetIncome?.map((item1) => {
// Find the corresponding item in data2 based on "FY"
const item2 = tableForecastNetIncome?.find(
(item2) => item2?.FY === item1?.FY,
);
// If the value in data1 is null, replace it with the value from data2
return {
FY: item1.FY,
val: item1.val === null ? item2.val : item1.val,
};
});
//============================//
//EPS Data //EPS Data
filteredData?.forEach((item) => { filteredData?.forEach((item) => {
tableActualEPS?.push({ tableActualEPS?.push({
@ -576,11 +635,6 @@
val: item?.estimatedEpsAvg, val: item?.estimatedEpsAvg,
}); });
}); });
//Values coincide with table values for crosscheck
revenueAvgGrowthList = computeGrowthList(
tableActualRevenue,
tableCombinedRevenue,
);
tableCombinedEPS = tableActualEPS?.map((item1) => { tableCombinedEPS = tableActualEPS?.map((item1) => {
// Find the corresponding item in data2 based on "FY" // Find the corresponding item in data2 based on "FY"
@ -593,6 +647,15 @@
}; };
}); });
//Values coincide with table values for crosscheck
revenueAvgGrowthList = computeGrowthList(
tableActualRevenue,
tableCombinedRevenue,
);
netIncomeAvgGrowthList = computeGrowthList(
tableActualNetIncome,
tableCombinedNetIncome,
);
epsAvgGrowthList = computeGrowthList(tableActualEPS, tableCombinedEPS); epsAvgGrowthList = computeGrowthList(tableActualEPS, tableCombinedEPS);
} }
@ -606,6 +669,7 @@
$analystEstimateComponent = true; $analystEstimateComponent = true;
getPlotOptions("Revenue"); getPlotOptions("Revenue");
getPlotOptions("EPS"); getPlotOptions("EPS");
getPlotOptions("NetIncome");
} else { } else {
$analystEstimateComponent = false; $analystEstimateComponent = false;
} }
@ -749,25 +813,59 @@
</td> </td>
{/each} {/each}
</tr> </tr>
<!-- <tr class="bg-[#27272A] border-b-[#27272A]">
<tr class="bg-[#09090B] border-b-[#09090B]">
<th <th
class="bg-[#09090B] text-sm sm:text-[1rem] whitespace-nowrap text-white text-start font-medium border-b border-[#09090B]" class="text-white whitespace-nowrap text-sm sm:text-[1rem] text-start font-medium bg-[#27272A] border-b border-[#27272A]"
>Forward PE</th
> >
{#each tableForecastEPS as item} Net Income
</th>
{#each tableCombinedNetIncome as item}
<td <td
class="text-white text-sm sm:text-[1rem] text-end font-medium bg-[#09090B]" class="text-white text-sm sm:text-[1rem] text-end font-medium border-b border-[#27272A] bg-[#09090B]"
> >
{item?.forwardPe === "0.00" || {item?.val === "0.00" ||
item?.forwardPe === null || item?.val === null ||
item?.forwardPe === 0 item?.val === 0
? "-" ? "n/a"
: abbreviateNumber(item.forwardPe)} : abbreviateNumber(item?.val.toFixed(2))}
</td>
{/each}
</tr>
<tr class="bg-[#27272A] border-b-[#27272A]">
<th
class="bg-[#27272A] whitespace-nowrap text-sm sm:text-[1rem] text-white text-start font-medium border-b border-[#27272A]"
>
Net Income Growth
</th>
{#each computeGrowthList(tableActualNetIncome, tableCombinedNetIncome) as item, index}
<td
class="text-white text-sm sm:text-[1rem] text-end font-medium bg-[#09090B]"
>
{#if index === 0 || item?.growth === null}
n/a
{:else if tableActualNetIncome[index]?.val === null}
<span
class="text-orange-400 {item?.growth > 0
? "before:content-['+']"
: ''}"
>
{item?.growth}%&#42;
</span>
{:else}
<span
class={item?.growth > 0
? "text-[#00FC50] before:content-['+']"
: item?.growth < 0
? "text-[#FF2F1F]"
: ""}
>
{item?.growth}%
</span>
{/if}
</td> </td>
{/each} {/each}
</tr> </tr>
-->
<tr class="bg-[#27272A] border-b-[#27272A]"> <tr class="bg-[#27272A] border-b-[#27272A]">
<th <th
@ -818,7 +916,7 @@
{/if} {/if}
<div class="space-y-6 lg:grid lg:grid-cols-2 lg:gap-6 lg:space-y-0 mt-10"> <div class="space-y-6 lg:grid lg:grid-cols-2 lg:gap-6 lg:space-y-0 mt-10">
<Lazy> <Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
<EstimationGraph <EstimationGraph
userTier={data?.user?.tier} userTier={data?.user?.tier}
title="Revenue" title="Revenue"
@ -830,7 +928,7 @@
/> />
</Lazy> </Lazy>
<Lazy> <Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
<EstimationGraph <EstimationGraph
userTier={data?.user?.tier} userTier={data?.user?.tier}
title="Revenue Growth" title="Revenue Growth"
@ -839,10 +937,12 @@
highDataList={highRevenueList} highDataList={highRevenueList}
avgDataList={avgRevenueList} avgDataList={avgRevenueList}
lowDataList={lowRevenueList} lowDataList={lowRevenueList}
avgGrowthList={revenueAvgGrowthList}
graphType="growth"
/> />
</Lazy> </Lazy>
<Lazy> <Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
<EstimationGraph <EstimationGraph
userTier={data?.user?.tier} userTier={data?.user?.tier}
title="EPS" title="EPS"
@ -854,7 +954,7 @@
/> />
</Lazy> </Lazy>
<Lazy> <Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
<EstimationGraph <EstimationGraph
userTier={data?.user?.tier} userTier={data?.user?.tier}
title="EPS Growth" title="EPS Growth"
@ -863,6 +963,34 @@
highDataList={highEPSList} highDataList={highEPSList}
avgDataList={avgEPSList} avgDataList={avgEPSList}
lowDataList={lowEPSList} lowDataList={lowEPSList}
avgGrowthList={epsAvgGrowthList}
graphType="growth"
/>
</Lazy>
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
<EstimationGraph
userTier={data?.user?.tier}
title="Net Income"
options={optionsNetIncome}
tableDataList={netIncomeDateList}
highDataList={highNetIncomeList}
avgDataList={avgNetIncomeList}
lowDataList={lowNetIncomeList}
/>
</Lazy>
<Lazy fadeOption={{ delay: 100, duration: 100 }} keep={true}>
<EstimationGraph
userTier={data?.user?.tier}
title="Net Income Growth"
options={optionsNetIncomeGrowth}
tableDataList={netIncomeDateList}
highDataList={highNetIncomeList}
avgDataList={avgNetIncomeList}
lowDataList={lowNetIncomeList}
avgGrowthList={netIncomeAvgGrowthList}
graphType="growth"
/> />
</Lazy> </Lazy>
</div> </div>

View File

@ -4,7 +4,7 @@
import { LineChart, CustomChart } from "echarts/charts"; import { LineChart, CustomChart } from "echarts/charts";
import { GridComponent, TooltipComponent } from "echarts/components"; import { GridComponent, TooltipComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers"; import { CanvasRenderer } from "echarts/renderers";
import { abbreviateNumber } from "$lib/utils"; import { abbreviateNumber, computeGrowthSingleList } from "$lib/utils";
use([ use([
LineChart, LineChart,
CustomChart, CustomChart,
@ -20,107 +20,242 @@
export let highDataList; export let highDataList;
export let avgDataList; export let avgDataList;
export let lowDataList; export let lowDataList;
export let avgGrowthList = [];
export let graphType = null;
</script> </script>
<div> {#if graphType !== "growth"}
<h2 class="mb-2 text-xl font-bold">{title} Forecast</h2> <div>
<div class="rounded-sm border p-2 border-gray-600"> <h2 class="mb-2 text-xl font-bold">{title} Forecast</h2>
<div class="app h-[275px] w-full"> <div class="rounded-sm border p-2 border-gray-600">
{#if options !== null} <div class="app h-[275px] w-full">
<Chart {init} {options} class="chart" /> {#if options !== null}
{/if} <Chart {init} {options} class="chart" />
</div> {/if}
<div class="mt-3 overflow-x-auto p-0 text-center sm:p-0.5 lg:mt-3.5"> </div>
<table class="w-full text-right"> <div class="mt-3 overflow-x-auto p-0 text-center sm:p-0.5 lg:mt-3.5">
<thead <table class="w-full text-right">
><tr <thead
class="border-b border-gray-600 align-bottom text-white font-normal" ><tr
><th class="p-1 text-left font-semibold text-sm sm:text-[1rem]" class="border-b border-gray-600 align-bottom text-white font-normal"
>{title}</th ><th class="p-1 text-left font-semibold text-sm sm:text-[1rem]"
> >{title}</th
{#each tableDataList as date, index}
<th class="p-1 font-semibold text-sm sm:text-[1rem]">
{#if index !== 0}{date}{/if}</th
> >
{/each} {#each tableDataList as date, index}
</tr></thead <th class="p-1 font-semibold text-sm sm:text-[1rem]">
> {#if index !== 0}{date}{/if}</th
<tbody >
><tr class="border-b border-gray-600 last:border-0" {/each}
><td class="whitespace-nowrap px-1 py-[3px] text-left">High</td> </tr></thead
{#each highDataList as item, index} >
<td class="px-1 py-[3px] text-sm sm:text-[1rem]"> <tbody
{#if index !== 0} ><tr class="border-b border-gray-600 last:border-0"
{#if userTier !== "Pro" && index >= highDataList?.length - 2} ><td class="whitespace-nowrap px-1 py-[3px] text-left">High</td>
<a class="inline-block ml-0.5 text-white" href="/pricing" {#each highDataList as item, index}
>Pro<svg <td class="px-1 py-[3px] text-sm sm:text-[1rem]">
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]" {#if index !== 0}
xmlns="http://www.w3.org/2000/svg" {#if userTier !== "Pro" && index >= highDataList?.length - 2}
viewBox="0 0 24 24" <a class="inline-block ml-0.5 text-white" href="/pricing"
><path >Pro<svg
fill="currentColor" class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z" xmlns="http://www.w3.org/2000/svg"
/></svg viewBox="0 0 24 24"
></a ><path
> fill="currentColor"
{:else} d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
{abbreviateNumber(item?.val)} /></svg
></a
>
{:else}
{abbreviateNumber(item?.val)}
{/if}
{/if} {/if}
{/if} </td>
</td> {/each}
{/each} </tr><tr class="border-b border-gray-600 last:border-0"
</tr><tr class="border-b border-gray-600 last:border-0" ><td class="whitespace-nowrap px-1 py-[3px] text-left">Avg</td>
><td class="whitespace-nowrap px-1 py-[3px] text-left">Avg</td> {#each avgDataList as item, index}
{#each avgDataList as item, index} <td class="px-1 py-[3px] text-sm sm:text-[1rem]">
<td class="px-1 py-[3px] text-sm sm:text-[1rem]"> {#if index !== 0}
{#if index !== 0} {#if userTier !== "Pro" && index >= avgDataList?.length - 2}
{#if userTier !== "Pro" && index >= avgDataList?.length - 2} <a class="inline-block ml-0.5 text-white" href="/pricing"
<a class="inline-block ml-0.5 text-white" href="/pricing" >Pro<svg
>Pro<svg class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
viewBox="0 0 24 24" ><path
><path fill="currentColor"
fill="currentColor" d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z" /></svg
/></svg ></a
></a >
> {:else}
{:else} {abbreviateNumber(item?.val)}
{abbreviateNumber(item?.val)} {/if}
{/if} {/if}
{/if} </td>
</td> {/each}
{/each} </tr><tr class="border-b border-gray-600 last:border-0"
</tr><tr class="border-b border-gray-600 last:border-0" ><td class="whitespace-nowrap px-1 py-[3px] text-left">Low</td>
><td class="whitespace-nowrap px-1 py-[3px] text-left">Low</td> {#each lowDataList as item, index}
{#each lowDataList as item, index} <td class="px-1 py-[3px] text-sm sm:text-[1rem]">
<td class="px-1 py-[3px] text-sm sm:text-[1rem]"> {#if index !== 0}
{#if index !== 0} {#if userTier !== "Pro" && index >= lowDataList?.length - 2}
{#if userTier !== "Pro" && index >= lowDataList?.length - 2} <a class="inline-block ml-0.5 text-white" href="/pricing"
<a class="inline-block ml-0.5 text-white" href="/pricing" >Pro<svg
>Pro<svg class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
viewBox="0 0 24 24" ><path
><path fill="currentColor"
fill="currentColor" d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z" /></svg
/></svg ></a
></a >
> {:else}
{:else} {abbreviateNumber(item?.val)}
{abbreviateNumber(item?.val)} {/if}
{/if} {/if}
{/if} </td>
</td> {/each}
{/each} </tr></tbody
</tr></tbody >
> </table>
</table> </div>
</div> </div>
</div> </div>
</div> {:else}
<div>
<h2 class="mb-2 text-xl font-bold">{title}</h2>
<div class="rounded-sm border p-2 border-gray-600">
<div class="app h-[275px] w-full">
{#if options !== null}
<Chart {init} {options} class="chart" />
{/if}
</div>
<div class="mt-3 overflow-x-auto p-0 text-center sm:p-0.5 lg:mt-3.5">
<table class="w-full text-right">
<thead
><tr
class="border-b border-gray-600 align-bottom text-white font-normal whitespace-nowrap"
><th class="p-1 text-left font-semibold text-sm sm:text-[1rem]"
>{title}</th
>
{#each tableDataList as date, index}
<th class="p-1 font-semibold text-sm sm:text-[1rem]"
>{#if index !== 0}{date}{/if}</th
>
{/each}
</tr></thead
>
<tbody
><tr class="border-b border-gray-600 last:border-0"
><td class="whitespace-nowrap px-1 py-[3px] text-left">High</td>
{#each computeGrowthSingleList(highDataList, avgDataList) as item, index}
<td class="px-1 py-[3px] text-sm sm:text-[1rem]">
{#if index !== 0}
{#if userTier !== "Pro" && index >= highDataList?.length - 2}
<a class="inline-block ml-0.5 text-white" href="/pricing"
>Pro<svg
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/></svg
></a
>
{:else}
<span
class={item?.growth !== null && item?.growth > 0
? "text-[#00FC50] before:content-['+']"
: item?.growth < 0
? "text-[#FF2F1F]"
: "text-white"}
>
{item?.growth !== null && Math.abs(item?.growth - 0) > 0
? abbreviateNumber(item?.growth) + "%"
: "-"}
</span>
{/if}
{/if}
</td>
{/each}
</tr><tr class="border-b border-gray-600 last:border-0"
><td class="whitespace-nowrap px-1 py-[3px] text-left">Avg</td>
{#each avgGrowthList?.filter((item) => item.FY >= 24) as item, index}
<td class="px-1 py-[3px] text-sm sm:text-[1rem]">
{#if index !== 0}
{#if userTier !== "Pro" && index >= avgDataList?.length - 2}
<a class="inline-block ml-0.5 text-white" href="/pricing"
>Pro<svg
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/></svg
></a
>
{:else}
<span
class={item?.growth !== null && item?.growth > 0
? "text-[#00FC50] before:content-['+']"
: item?.growth < 0
? "text-[#FF2F1F]"
: "text-white"}
>
{item?.growth !== null && Math.abs(item?.growth - 0) > 0
? abbreviateNumber(item?.growth) + "%"
: "-"}
</span>
{/if}
{/if}
</td>
{/each}
</tr><tr class="border-b border-gray-600 last:border-0"
><td class="whitespace-nowrap px-1 py-[3px] text-left">Low</td>
{#each computeGrowthSingleList(lowDataList, avgDataList) as item, index}
<td class="px-1 py-[3px] text-sm sm:text-[1rem]">
{#if index !== 0}
{#if userTier !== "Pro" && index >= lowDataList?.length - 2}
<a class="inline-block ml-0.5 text-white" href="/pricing"
>Pro<svg
class="w-4 h-4 ml-0.5 mb-1 inline-block text-[#A3A3A3]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/></svg
></a
>
{:else}
<span
class={item?.growth !== null && item?.growth > 0
? "text-[#00FC50] before:content-['+']"
: item?.growth < 0
? "text-[#FF2F1F]"
: "text-white"}
>
{item?.growth !== null && Math.abs(item?.growth - 0) > 0
? abbreviateNumber(item?.growth) + "%"
: "-"}
</span>
{/if}
{/if}
</td>
{/each}
</tr></tbody
>
</table>
</div>
</div>
</div>
{/if}
<style> <style>
.app { .app {

View File

@ -20,6 +20,43 @@ type FlyAndScaleParams = {
export const computeGrowthSingleList = (data, actualList) => {
// Initialize the result list
let resultList = [];
for (let i = 0; i < data?.length; i++) {
const currentData = data[i];
// Find the corresponding actual data from one FY back
const correspondingActual = actualList?.find(
(entry) => Number(entry.FY) === Number(currentData.FY) - 1,
);
// Calculate growth if a matching entry exists in actualList
let growth = null;
if (
correspondingActual &&
correspondingActual?.val !== null &&
currentData.val !== null
) {
growth = (
((currentData?.val - correspondingActual?.val) /
Math.abs(correspondingActual?.val)) *
100
)?.toFixed(2);
}
// Push the result for this FY
resultList.push({
FY: currentData.FY,
val: currentData.val,
growth: growth !== null ? Number(growth) : null, // Convert growth to number or leave as null
});
}
return resultList;
}
export const compareTimes = (time1, time2) => { export const compareTimes = (time1, time2) => {
const [hours1, minutes1] = time1.split(":").map(Number); const [hours1, minutes1] = time1.split(":").map(Number);

View File

@ -33,7 +33,6 @@ export const load = async ({ locals, params }) => {
}); });
const output = await response.json(); const output = await response.json();
return output; return output;
}; };