bugfixing
This commit is contained in:
parent
6ff737218a
commit
be6db79756
@ -18,7 +18,6 @@
|
||||
import Download from "lucide-svelte/icons/download";
|
||||
|
||||
import { goto } from "$app/navigation";
|
||||
import SEO from "$lib/components/SEO.svelte";
|
||||
|
||||
export let data;
|
||||
export let title;
|
||||
@ -110,6 +109,7 @@
|
||||
const headers = properties.map((prop) => prop.label);
|
||||
rows.unshift(headers);
|
||||
|
||||
const fileTitle = title?.toLowerCase()?.trim()?.replace(/\s+/g, "_");
|
||||
// Check the format to export
|
||||
if (format.toLowerCase() === "csv") {
|
||||
const csvContent = rows.map((row) => row.join(",")).join("\n");
|
||||
@ -121,7 +121,7 @@
|
||||
a.href = url;
|
||||
a.download =
|
||||
$stockTicker.toLowerCase() +
|
||||
`${$selectedTimePeriod === "annual" ? "_annual" : $selectedTimePeriod === "quarterly" ? "_quarter" : "_ttm"}_income_statement.csv`;
|
||||
`${$selectedTimePeriod === "annual" ? "_annual" : $selectedTimePeriod === "quarterly" ? "_quarter" : "_ttm"}_${fileTitle}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
@ -207,11 +207,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<SEO
|
||||
title={`${$displayCompanyName} (${$stockTicker}) Financials - ${title}`}
|
||||
description={seoDescription}
|
||||
/>
|
||||
|
||||
<section class=" w-full overflow-hidden h-full">
|
||||
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
|
||||
<div
|
||||
|
||||
@ -1,34 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
coolMode,
|
||||
timeFrame,
|
||||
selectedTimePeriod,
|
||||
screenWidth,
|
||||
} from "$lib/store";
|
||||
import { removeCompanyStrings } from "$lib/utils";
|
||||
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||
//import * as XLSX from 'xlsx';
|
||||
import FinancialTable from "$lib/components/FinancialTable.svelte";
|
||||
import FinancialChart from "$lib/components/FinancialChart.svelte";
|
||||
import TableMode from "lucide-svelte/icons/grid-2x2";
|
||||
import ChartMode from "lucide-svelte/icons/chart-column-increasing";
|
||||
import Download from "lucide-svelte/icons/download";
|
||||
import { displayCompanyName, stockTicker } from "$lib/store";
|
||||
|
||||
import { goto } from "$app/navigation";
|
||||
import SEO from "$lib/components/SEO.svelte";
|
||||
import FinancialSection from "$lib/components/FinancialSection.svelte";
|
||||
|
||||
export let data;
|
||||
|
||||
let switchDate = false;
|
||||
let tableList = [];
|
||||
let processedData = {};
|
||||
|
||||
let financialData = [];
|
||||
let fullStatement = [];
|
||||
|
||||
const statementConfig = [
|
||||
{
|
||||
propertyName: "revenue",
|
||||
@ -120,176 +97,6 @@
|
||||
label: "Depreciation & Amortization",
|
||||
},
|
||||
];
|
||||
|
||||
const fields = statementConfig.map((item) => ({
|
||||
label: item.label,
|
||||
key: item.propertyName,
|
||||
}));
|
||||
function toggleMode() {
|
||||
$coolMode = !$coolMode;
|
||||
}
|
||||
|
||||
const getCurrentYear = () => new Date()?.getFullYear();
|
||||
|
||||
const filterStatement = (fullStatement, timeFrame, switchDate) => {
|
||||
const currentYear = getCurrentYear();
|
||||
|
||||
let filtered = [...(fullStatement || [])];
|
||||
|
||||
switch (timeFrame) {
|
||||
case "5Y":
|
||||
filtered = filtered.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 5,
|
||||
);
|
||||
break;
|
||||
case "10Y":
|
||||
filtered = filtered.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 10,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// Sort by date depending on switchDate
|
||||
filtered.sort((a, b) => {
|
||||
const dateA = new Date(a.date || a.fiscalDate || a.fiscalYear);
|
||||
const dateB = new Date(b.date || b.fiscalDate || b.fiscalYear);
|
||||
return switchDate
|
||||
? dateA - dateB // earliest to latest
|
||||
: dateB - dateA; // latest to earliest
|
||||
});
|
||||
|
||||
return filtered;
|
||||
};
|
||||
|
||||
fullStatement = data?.getData;
|
||||
|
||||
const exportFundamentalData = (format = "csv") => {
|
||||
if (["Pro", "Plus"]?.includes(data?.user?.tier)) {
|
||||
const data = fullStatement;
|
||||
if (!data || data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let properties = [
|
||||
{
|
||||
key: $selectedTimePeriod === "annual" ? "fiscalYear" : "date",
|
||||
label: $selectedTimePeriod === "annual" ? "Year" : "Quarter",
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < statementConfig?.length; i++) {
|
||||
properties.push({
|
||||
key: statementConfig[i]?.propertyName,
|
||||
label: statementConfig[i]?.label,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to handle special cases
|
||||
|
||||
// Create rows for CSV/Excel
|
||||
let rows = data.map((item) =>
|
||||
properties?.map((property) => item[property?.key] || 0),
|
||||
);
|
||||
|
||||
// Include headers
|
||||
const headers = properties.map((prop) => prop.label);
|
||||
rows.unshift(headers);
|
||||
|
||||
// Check the format to export
|
||||
if (format.toLowerCase() === "csv") {
|
||||
const csvContent = rows.map((row) => row.join(",")).join("\n");
|
||||
const blob = new Blob([csvContent], {
|
||||
type: "data:text/csv;charset=utf-8",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download =
|
||||
$stockTicker.toLowerCase() +
|
||||
`${$selectedTimePeriod === "annual" ? "_annual" : $selectedTimePeriod === "quarterly" ? "_quarter" : "_ttm"}_income_statement.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} else {
|
||||
goto("/pricing");
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-process all data once instead of in each component
|
||||
function preprocessFinancialData() {
|
||||
processedData = {};
|
||||
|
||||
// Precompute mapping from propertyName to config for quick lookup
|
||||
const configMap = {};
|
||||
statementConfig.forEach((item) => {
|
||||
if (item && item.propertyName) {
|
||||
configMap[item.propertyName] = item;
|
||||
}
|
||||
});
|
||||
|
||||
// Precompute xList from income (reverse order)
|
||||
const xList = [];
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const year = statement.fiscalYear.slice(-2);
|
||||
const quarter = statement.period;
|
||||
xList.push(
|
||||
$selectedTimePeriod === "annual"
|
||||
? "FY" + year
|
||||
: "FY" + year + " " + quarter,
|
||||
);
|
||||
}
|
||||
|
||||
// Process each field using precomputed config and xList
|
||||
fields.forEach((field) => {
|
||||
const statementKey = field.key;
|
||||
const config = configMap[statementKey];
|
||||
if (!config) return;
|
||||
|
||||
const valueList = [];
|
||||
// Loop through financialData in reverse to match xList order
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const rawValue = Number(statement[config.propertyName]);
|
||||
// Round to two decimals
|
||||
const value = parseFloat(rawValue.toFixed(2));
|
||||
valueList.push(value);
|
||||
}
|
||||
|
||||
processedData[statementKey] = {
|
||||
xList, // re-use the precomputed labels
|
||||
valueList,
|
||||
labelName: config.label,
|
||||
};
|
||||
});
|
||||
|
||||
// Build tableList once for all charts and sort by date (newest first)
|
||||
tableList = financialData?.map((statement) => ({
|
||||
date: statement.date,
|
||||
// Add more properties if needed
|
||||
}));
|
||||
|
||||
tableList.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($timeFrame || $selectedTimePeriod) {
|
||||
if ($selectedTimePeriod === "annual") {
|
||||
fullStatement = data?.getData?.annual;
|
||||
} else if ($selectedTimePeriod === "quarterly") {
|
||||
fullStatement = data?.getData?.quarter;
|
||||
} else if ($selectedTimePeriod === "ttm") {
|
||||
fullStatement = data?.getData?.ttm;
|
||||
} else {
|
||||
fullStatement = data?.getData?.annual;
|
||||
}
|
||||
|
||||
financialData = filterStatement(fullStatement, $timeFrame, switchDate);
|
||||
preprocessFinancialData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SEO
|
||||
@ -297,213 +104,4 @@
|
||||
description={`Detailed annual, quarterly and trailing income statement for ${$displayCompanyName} (${$stockTicker}). See many years of revenue, expenses and profits or losses.`}
|
||||
/>
|
||||
|
||||
<section class=" w-full overflow-hidden h-full">
|
||||
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
|
||||
<div
|
||||
class="w-full relative flex justify-center items-center overflow-hidden"
|
||||
>
|
||||
<main class="w-full">
|
||||
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
|
||||
<div
|
||||
class="mb-3 sm:mb-0 flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
||||
>
|
||||
<h1 class="text-xl sm:text-2xl font-bold">
|
||||
{removeCompanyStrings($displayCompanyName)} Income Statement
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
{#if financialData?.length > 0}
|
||||
<div class="flex flex-col md:flex-row items-end justify-between">
|
||||
<span
|
||||
class="text-xs sm:text-sm order-1 sm:order-0 mt-5 sm:mt-0 text-gray-600 dark:text-gray-400 w-full"
|
||||
>
|
||||
Financials in {financialData?.at(0)?.reportedCurrency}. Fiscal
|
||||
year is
|
||||
{data?.getProfileData?.fiscalYearRange}.
|
||||
</span>
|
||||
<div class="flex flex-row items-center justify-end w-full">
|
||||
<Button
|
||||
on:click={toggleMode}
|
||||
class=" shadow-sm w-full max-w-36 sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-4 py-1.5 rounded-md truncate"
|
||||
>
|
||||
{#if $coolMode}
|
||||
<TableMode class="w-4.5 h-4.5" />
|
||||
<span class="ml-2 text-sm"> Table Mode </span>
|
||||
{:else}
|
||||
<ChartMode class="w-4.5 h-4.5" />
|
||||
<span class="ml-2 text-sm"> Chart Mode </span>
|
||||
{/if}</Button
|
||||
>
|
||||
|
||||
<Button
|
||||
on:click={() => (switchDate = !switchDate)}
|
||||
class="ml-2 shadow-sm w-48 sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-4 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<svg
|
||||
class="shrink-0 w-5 h-5 pointer-events-none m-auto"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
style="max-width:40px"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||
></path></svg
|
||||
></Button
|
||||
>
|
||||
<div class="ml-2 relative inline-block">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
builders={[builder]}
|
||||
class="flex-shrink-0 shadow-sm w-full sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<span class="truncate">{$timeFrame}</span>
|
||||
<svg
|
||||
class="-mr-1 ml-1 h-5 w-5 xs:ml-2 inline-block"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
style="max-width:40px"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-56 h-fit max-h-72 overflow-y-auto scroller"
|
||||
>
|
||||
<DropdownMenu.Label
|
||||
class="text-muted dark:text-gray-400 font-normal"
|
||||
>
|
||||
Select time frame
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "5Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
5 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "10Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
10 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "MAX")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
Max
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
on:click={() => exportFundamentalData("csv")}
|
||||
class="shadow-sm ml-2 w-20 sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
{#if $screenWidth < 640}
|
||||
<Download class="w-4.5 h-4.5 flex-shrink-0 m-auto" />
|
||||
{:else}
|
||||
<span class="truncate">Download</span>
|
||||
<svg
|
||||
class="{['Pro', 'Plus']?.includes(data?.user?.tier)
|
||||
? 'hidden'
|
||||
: ''} ml-1 -mt-0.5 w-3.5 h-3.5"
|
||||
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
|
||||
>
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if $coolMode}
|
||||
<div class="grid gap-5 xs:gap-6 lg:grid-cols-3 lg:gap-3">
|
||||
{#each fields as item, i}
|
||||
<FinancialChart
|
||||
data={financialData}
|
||||
{statementConfig}
|
||||
displayStatement={item?.key}
|
||||
filterRule={$selectedTimePeriod}
|
||||
{processedData}
|
||||
color={["#ff00cc", "#37ff00", "#0c63e7", "#07c8f9"][
|
||||
i % 4
|
||||
]}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="w-full rounded-none sm:rounded-md m-auto overflow-x-auto no-scrollbar"
|
||||
>
|
||||
<table
|
||||
class="table table-sm table-compact no-scrollbar rounded-none sm:rounded-md w-full border border-gray-300 dark:border-gray-800 m-auto"
|
||||
>
|
||||
<thead class="text-muted dark:text-white dark:bg-default">
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold pr-10"
|
||||
>Fiscal Year</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
{#if $selectedTimePeriod === "annual"}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{"FY" + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{:else}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end"
|
||||
>
|
||||
{item?.period + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold"
|
||||
>Period Ending</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
<td
|
||||
class=" font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{new Date(item?.date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row -->
|
||||
<FinancialTable data={financialData} {fields} />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<FinancialSection {data} title="Income Statement" {statementConfig} />
|
||||
|
||||
@ -1,28 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
coolMode,
|
||||
timeFrame,
|
||||
selectedTimePeriod,
|
||||
} from "$lib/store";
|
||||
import { removeCompanyStrings } from "$lib/utils";
|
||||
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||
//import * as XLSX from 'xlsx';
|
||||
import FinancialTable from "$lib/components/FinancialTable.svelte";
|
||||
import FinancialChart from "$lib/components/FinancialChart.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { displayCompanyName, stockTicker } from "$lib/store";
|
||||
|
||||
import SEO from "$lib/components/SEO.svelte";
|
||||
import FinancialSection from "$lib/components/FinancialSection.svelte";
|
||||
|
||||
export let data;
|
||||
|
||||
let tableList = [];
|
||||
let processedData = {};
|
||||
|
||||
let financialData = [];
|
||||
let fullStatement = [];
|
||||
|
||||
const statementConfig = [
|
||||
{
|
||||
propertyName: "cashAndCashEquivalents",
|
||||
@ -187,380 +170,11 @@
|
||||
text: "Total Investments encompass all a company's financial assets, such as stocks, bonds, and real estate, reflecting its financial health and growth potential. Calculated by summing up these asset values, it's a key indicator of a company's financial strength.",
|
||||
},
|
||||
];
|
||||
|
||||
const fields = statementConfig.map((item) => ({
|
||||
label: item.label,
|
||||
key: item.propertyName,
|
||||
}));
|
||||
function toggleMode() {
|
||||
$coolMode = !$coolMode;
|
||||
}
|
||||
|
||||
const getCurrentYear = () => new Date()?.getFullYear();
|
||||
|
||||
const filterStatement = (fullStatement, timeFrame) => {
|
||||
const currentYear = getCurrentYear();
|
||||
|
||||
switch (timeFrame) {
|
||||
case "5Y":
|
||||
return fullStatement?.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 5,
|
||||
);
|
||||
case "10Y":
|
||||
return fullStatement?.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 10,
|
||||
);
|
||||
default:
|
||||
return fullStatement;
|
||||
}
|
||||
};
|
||||
|
||||
fullStatement = data?.getData;
|
||||
|
||||
const exportFundamentalData = (format = "csv") => {
|
||||
if (["Pro", "Plus"]?.includes(data?.user?.tier)) {
|
||||
const data = fullStatement;
|
||||
if (!data || data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let properties = [
|
||||
{
|
||||
key: $selectedTimePeriod === "annual" ? "fiscalYear" : "date",
|
||||
label: $selectedTimePeriod === "annual" ? "Year" : "Quarter",
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < statementConfig?.length; i++) {
|
||||
properties.push({
|
||||
key: statementConfig[i]?.propertyName,
|
||||
label: statementConfig[i]?.label,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to handle special cases
|
||||
|
||||
// Create rows for CSV/Excel
|
||||
let rows = data.map((item) =>
|
||||
properties?.map((property) => item[property?.key] || 0),
|
||||
);
|
||||
|
||||
// Include headers
|
||||
const headers = properties.map((prop) => prop.label);
|
||||
rows.unshift(headers);
|
||||
|
||||
// Check the format to export
|
||||
if (format.toLowerCase() === "csv") {
|
||||
const csvContent = rows.map((row) => row.join(",")).join("\n");
|
||||
const blob = new Blob([csvContent], {
|
||||
type: "data:text/csv;charset=utf-8",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download =
|
||||
$stockTicker.toLowerCase() +
|
||||
`${$selectedTimePeriod === "annual" ? "_annual" : $selectedTimePeriod === "quarterly" ? "_quarter" : "_ttm"}_balance_sheet_statement.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} else {
|
||||
goto("/pricing");
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-process all data once instead of in each component
|
||||
function preprocessFinancialData() {
|
||||
processedData = {};
|
||||
|
||||
// Precompute mapping from propertyName to config for quick lookup
|
||||
const configMap = {};
|
||||
statementConfig.forEach((item) => {
|
||||
if (item && item.propertyName) {
|
||||
configMap[item.propertyName] = item;
|
||||
}
|
||||
});
|
||||
|
||||
// Precompute xList from income (reverse order)
|
||||
const xList = [];
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const year = statement.fiscalYear.slice(-2);
|
||||
const quarter = statement.period;
|
||||
xList.push(
|
||||
$selectedTimePeriod === "annual"
|
||||
? "FY" + year
|
||||
: "FY" + year + " " + quarter,
|
||||
);
|
||||
}
|
||||
|
||||
// Process each field using precomputed config and xList
|
||||
fields.forEach((field) => {
|
||||
const statementKey = field.key;
|
||||
const config = configMap[statementKey];
|
||||
if (!config) return;
|
||||
|
||||
const valueList = [];
|
||||
// Loop through financialData in reverse to match xList order
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const rawValue = Number(statement[config.propertyName]);
|
||||
// Round to two decimals
|
||||
const value = parseFloat(rawValue.toFixed(2));
|
||||
valueList.push(value);
|
||||
}
|
||||
|
||||
processedData[statementKey] = {
|
||||
xList, // re-use the precomputed labels
|
||||
valueList,
|
||||
labelName: config.label,
|
||||
};
|
||||
});
|
||||
|
||||
// Build tableList once for all charts and sort by date (newest first)
|
||||
tableList = financialData?.map((statement) => ({
|
||||
date: statement.date,
|
||||
// Add more properties if needed
|
||||
}));
|
||||
|
||||
tableList.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($timeFrame || $selectedTimePeriod) {
|
||||
if ($selectedTimePeriod === "annual") {
|
||||
fullStatement = data?.getData?.annual;
|
||||
} else if ($selectedTimePeriod === "quarterly") {
|
||||
fullStatement = data?.getData?.quarter;
|
||||
} else if ($selectedTimePeriod === "ttm") {
|
||||
fullStatement = data?.getData?.ttm;
|
||||
} else {
|
||||
fullStatement = data?.getData?.annual;
|
||||
}
|
||||
|
||||
financialData = filterStatement(fullStatement, $timeFrame);
|
||||
preprocessFinancialData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SEO
|
||||
title={`${$displayCompanyName} (${$stockTicker}) Cash Flow Statement`}
|
||||
description={`Detailed cash flow statements for ${$displayCompanyName} (${$stockTicker}), including operating cash flow, capex and free cash flow.`}
|
||||
title={`${$displayCompanyName} (${$stockTicker}) Balance Sheet Statement`}
|
||||
description={`Detailed balance sheet statements for ${$displayCompanyName} (${$stockTicker}), including cash, debt, assets, liabilities, and book value.`}
|
||||
/>
|
||||
|
||||
<section class=" w-full overflow-hidden h-full">
|
||||
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
|
||||
<div
|
||||
class="w-full relative flex justify-center items-center overflow-hidden"
|
||||
>
|
||||
<main class="w-full">
|
||||
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
|
||||
<div
|
||||
class="mb-3 sm:mb-0 flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
||||
>
|
||||
<h1 class="text-xl sm:text-2xl font-bold">
|
||||
{removeCompanyStrings($displayCompanyName)} Income Statement
|
||||
</h1>
|
||||
<label class="inline-flex sm:hidden mt-4 cursor-pointer relative">
|
||||
<input
|
||||
on:click={toggleMode}
|
||||
type="checkbox"
|
||||
checked={$coolMode}
|
||||
value={$coolMode}
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-400 rounded-full peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#1563F9]"
|
||||
></div>
|
||||
{#if $coolMode}
|
||||
<span class="ml-2 text-sm"> Table Mode </span>
|
||||
{:else}
|
||||
<span class="ml-2 text-sm"> Chart Mode </span>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
{#if financialData?.length > 0}
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-start sm:items-end sm:justify-between"
|
||||
>
|
||||
<span
|
||||
class="text-xs sm:text-sm order-1 sm:order-0 mt-5 sm:mt-0 text-gray-600 dark:text-gray-400 w-full"
|
||||
>
|
||||
Financials in {financialData?.at(0)?.reportedCurrency}. Fiscal
|
||||
year is
|
||||
{data?.getProfileData?.fiscalYearRange}.
|
||||
</span>
|
||||
<div class="flex flex-row items-center justify-end w-full">
|
||||
<label
|
||||
class="hidden sm:inline-flex ml-auto mt-2 sm:mt-0 cursor-pointer relative"
|
||||
>
|
||||
<input
|
||||
on:click={toggleMode}
|
||||
type="checkbox"
|
||||
checked={$coolMode}
|
||||
value={$coolMode}
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-400 rounded-full peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#1563F9]"
|
||||
></div>
|
||||
{#if $coolMode}
|
||||
<span class="ml-2 text-sm"> Table Mode </span>
|
||||
{:else}
|
||||
<span class="ml-2 text-sm"> Chart Mode </span>
|
||||
{/if}
|
||||
</label>
|
||||
<div class="ml-auto sm:ml-5 relative inline-block">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
builders={[builder]}
|
||||
class="shadow-sm w-full sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<span class="truncate">{$timeFrame}</span>
|
||||
<svg
|
||||
class="-mr-1 ml-1 h-5 w-5 xs:ml-2 inline-block"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
style="max-width:40px"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-56 h-fit max-h-72 overflow-y-auto scroller"
|
||||
>
|
||||
<DropdownMenu.Label
|
||||
class="text-muted dark:text-gray-400 font-normal"
|
||||
>
|
||||
Select time frame
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "5Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
5 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "10Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
10 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "MAX")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
Max
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
on:click={() => exportFundamentalData("csv")}
|
||||
class="shadow-sm ml-2 w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<span class="truncate">Download</span>
|
||||
<svg
|
||||
class="{['Pro', 'Plus']?.includes(data?.user?.tier)
|
||||
? 'hidden'
|
||||
: ''} ml-1 -mt-0.5 w-3.5 h-3.5"
|
||||
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
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if $coolMode}
|
||||
<div class="grid gap-5 xs:gap-6 lg:grid-cols-3 lg:gap-3">
|
||||
{#each fields as item, i}
|
||||
<FinancialChart
|
||||
data={financialData}
|
||||
{statementConfig}
|
||||
displayStatement={item?.key}
|
||||
filterRule={$selectedTimePeriod}
|
||||
{processedData}
|
||||
color={["#ff00cc", "#37ff00", "#0c63e7", "#07c8f9"][
|
||||
i % 4
|
||||
]}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="w-full rounded-none sm:rounded-md m-auto overflow-x-auto no-scrollbar"
|
||||
>
|
||||
<table
|
||||
class="table table-sm table-compact no-scrollbar rounded-none sm:rounded-md w-full border border-gray-300 dark:border-gray-800 m-auto"
|
||||
>
|
||||
<thead class="text-muted dark:text-white dark:bg-default">
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold pr-10"
|
||||
>Fiscal Year</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
{#if $selectedTimePeriod === "annual"}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{"FY" + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{:else}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end"
|
||||
>
|
||||
{item?.period + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold"
|
||||
>Period Ending</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
<td
|
||||
class=" font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{new Date(item?.date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row -->
|
||||
<FinancialTable data={financialData} {fields} />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<FinancialSection {data} title="Balance Sheet Statement" {statementConfig} />
|
||||
|
||||
@ -1,28 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
coolMode,
|
||||
timeFrame,
|
||||
selectedTimePeriod,
|
||||
} from "$lib/store";
|
||||
import { removeCompanyStrings } from "$lib/utils";
|
||||
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||
//import * as XLSX from 'xlsx';
|
||||
import FinancialTable from "$lib/components/FinancialTable.svelte";
|
||||
import FinancialChart from "$lib/components/FinancialChart.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { displayCompanyName, stockTicker } from "$lib/store";
|
||||
|
||||
import SEO from "$lib/components/SEO.svelte";
|
||||
import FinancialSection from "$lib/components/FinancialSection.svelte";
|
||||
|
||||
export let data;
|
||||
|
||||
let tableList = [];
|
||||
let processedData = {};
|
||||
|
||||
let financialData = [];
|
||||
let fullStatement = [];
|
||||
|
||||
const statementConfig = [
|
||||
{
|
||||
propertyName: "netIncome",
|
||||
@ -130,163 +113,6 @@
|
||||
text: "Free cash flow is the cash remaining after the company spends on everything required to maintain and grow the business. It is calculated by subtracting capital expenditures from operating cash flow.",
|
||||
},
|
||||
];
|
||||
|
||||
const fields = statementConfig.map((item) => ({
|
||||
label: item.label,
|
||||
key: item.propertyName,
|
||||
}));
|
||||
function toggleMode() {
|
||||
$coolMode = !$coolMode;
|
||||
}
|
||||
|
||||
const getCurrentYear = () => new Date()?.getFullYear();
|
||||
|
||||
const filterStatement = (fullStatement, timeFrame) => {
|
||||
const currentYear = getCurrentYear();
|
||||
|
||||
switch (timeFrame) {
|
||||
case "5Y":
|
||||
return fullStatement?.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 5,
|
||||
);
|
||||
case "10Y":
|
||||
return fullStatement?.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 10,
|
||||
);
|
||||
default:
|
||||
return fullStatement;
|
||||
}
|
||||
};
|
||||
|
||||
fullStatement = data?.getData;
|
||||
|
||||
const exportFundamentalData = (format = "csv") => {
|
||||
if (["Pro", "Plus"]?.includes(data?.user?.tier)) {
|
||||
const data = fullStatement;
|
||||
if (!data || data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let properties = [
|
||||
{
|
||||
key: $selectedTimePeriod === "annual" ? "fiscalYear" : "date",
|
||||
label: $selectedTimePeriod === "annual" ? "Year" : "Quarter",
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < statementConfig?.length; i++) {
|
||||
properties.push({
|
||||
key: statementConfig[i]?.propertyName,
|
||||
label: statementConfig[i]?.label,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to handle special cases
|
||||
|
||||
// Create rows for CSV/Excel
|
||||
let rows = data.map((item) =>
|
||||
properties?.map((property) => item[property?.key] || 0),
|
||||
);
|
||||
|
||||
// Include headers
|
||||
const headers = properties.map((prop) => prop.label);
|
||||
rows.unshift(headers);
|
||||
|
||||
// Check the format to export
|
||||
if (format.toLowerCase() === "csv") {
|
||||
const csvContent = rows.map((row) => row.join(",")).join("\n");
|
||||
const blob = new Blob([csvContent], {
|
||||
type: "data:text/csv;charset=utf-8",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download =
|
||||
$stockTicker.toLowerCase() +
|
||||
`${$selectedTimePeriod === "annual" ? "_annual" : $selectedTimePeriod === "quarterly" ? "_quarter" : "_ttm"}_cash_flow_statement.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} else {
|
||||
goto("/pricing");
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-process all data once instead of in each component
|
||||
function preprocessFinancialData() {
|
||||
processedData = {};
|
||||
|
||||
// Precompute mapping from propertyName to config for quick lookup
|
||||
const configMap = {};
|
||||
statementConfig.forEach((item) => {
|
||||
if (item && item.propertyName) {
|
||||
configMap[item.propertyName] = item;
|
||||
}
|
||||
});
|
||||
|
||||
// Precompute xList from income (reverse order)
|
||||
const xList = [];
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const year = statement.fiscalYear.slice(-2);
|
||||
const quarter = statement.period;
|
||||
xList.push(
|
||||
$selectedTimePeriod === "annual"
|
||||
? "FY" + year
|
||||
: "FY" + year + " " + quarter,
|
||||
);
|
||||
}
|
||||
|
||||
// Process each field using precomputed config and xList
|
||||
fields.forEach((field) => {
|
||||
const statementKey = field.key;
|
||||
const config = configMap[statementKey];
|
||||
if (!config) return;
|
||||
|
||||
const valueList = [];
|
||||
// Loop through financialData in reverse to match xList order
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const rawValue = Number(statement[config.propertyName]);
|
||||
// Round to two decimals
|
||||
const value = parseFloat(rawValue.toFixed(2));
|
||||
valueList.push(value);
|
||||
}
|
||||
|
||||
processedData[statementKey] = {
|
||||
xList, // re-use the precomputed labels
|
||||
valueList,
|
||||
labelName: config.label,
|
||||
};
|
||||
});
|
||||
|
||||
// Build tableList once for all charts and sort by date (newest first)
|
||||
tableList = financialData?.map((statement) => ({
|
||||
date: statement.date,
|
||||
// Add more properties if needed
|
||||
}));
|
||||
|
||||
tableList.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($timeFrame || $selectedTimePeriod) {
|
||||
if ($selectedTimePeriod === "annual") {
|
||||
fullStatement = data?.getData?.annual;
|
||||
} else if ($selectedTimePeriod === "quarterly") {
|
||||
fullStatement = data?.getData?.quarter;
|
||||
} else if ($selectedTimePeriod === "ttm") {
|
||||
fullStatement = data?.getData?.ttm;
|
||||
} else {
|
||||
fullStatement = data?.getData?.annual;
|
||||
}
|
||||
|
||||
financialData = filterStatement(fullStatement, $timeFrame);
|
||||
preprocessFinancialData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SEO
|
||||
@ -294,216 +120,4 @@
|
||||
description={`Detailed cash flow statements for ${$displayCompanyName} (${$stockTicker}), including operating cash flow, capex and free cash flow.`}
|
||||
/>
|
||||
|
||||
<section class=" w-full overflow-hidden h-full">
|
||||
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
|
||||
<div
|
||||
class="w-full relative flex justify-center items-center overflow-hidden"
|
||||
>
|
||||
<main class="w-full">
|
||||
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
|
||||
<div
|
||||
class="mb-3 sm:mb-0 flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
||||
>
|
||||
<h1 class="text-xl sm:text-2xl font-bold">
|
||||
{removeCompanyStrings($displayCompanyName)} Income Statement
|
||||
</h1>
|
||||
<label class="inline-flex sm:hidden mt-4 cursor-pointer relative">
|
||||
<input
|
||||
on:click={toggleMode}
|
||||
type="checkbox"
|
||||
checked={$coolMode}
|
||||
value={$coolMode}
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-400 rounded-full peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#1563F9]"
|
||||
></div>
|
||||
{#if $coolMode}
|
||||
<span class="ml-2 text-sm"> Table Mode </span>
|
||||
{:else}
|
||||
<span class="ml-2 text-sm"> Chart Mode </span>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
{#if financialData?.length > 0}
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-start sm:items-end sm:justify-between"
|
||||
>
|
||||
<span
|
||||
class="text-xs sm:text-sm order-1 sm:order-0 mt-5 sm:mt-0 text-gray-600 dark:text-gray-400 w-full"
|
||||
>
|
||||
Financials in {financialData?.at(0)?.reportedCurrency}. Fiscal
|
||||
year is
|
||||
{data?.getProfileData?.fiscalYearRange}.
|
||||
</span>
|
||||
<div class="flex flex-row items-center justify-end w-full">
|
||||
<label
|
||||
class="hidden sm:inline-flex ml-auto mt-2 sm:mt-0 cursor-pointer relative"
|
||||
>
|
||||
<input
|
||||
on:click={toggleMode}
|
||||
type="checkbox"
|
||||
checked={$coolMode}
|
||||
value={$coolMode}
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-400 rounded-full peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#1563F9]"
|
||||
></div>
|
||||
{#if $coolMode}
|
||||
<span class="ml-2 text-sm"> Table Mode </span>
|
||||
{:else}
|
||||
<span class="ml-2 text-sm"> Chart Mode </span>
|
||||
{/if}
|
||||
</label>
|
||||
<div class="ml-auto sm:ml-5 relative inline-block">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
builders={[builder]}
|
||||
class="shadow-sm w-full sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<span class="truncate">{$timeFrame}</span>
|
||||
<svg
|
||||
class="-mr-1 ml-1 h-5 w-5 xs:ml-2 inline-block"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
style="max-width:40px"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-56 h-fit max-h-72 overflow-y-auto scroller"
|
||||
>
|
||||
<DropdownMenu.Label
|
||||
class="text-muted dark:text-gray-400 font-normal"
|
||||
>
|
||||
Select time frame
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "5Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
5 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "10Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
10 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "MAX")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
Max
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
on:click={() => exportFundamentalData("csv")}
|
||||
class="shadow-sm ml-2 w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<span class="truncate">Download</span>
|
||||
<svg
|
||||
class="{['Pro', 'Plus']?.includes(data?.user?.tier)
|
||||
? 'hidden'
|
||||
: ''} ml-1 -mt-0.5 w-3.5 h-3.5"
|
||||
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
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if $coolMode}
|
||||
<div class="grid gap-5 xs:gap-6 lg:grid-cols-3 lg:gap-3">
|
||||
{#each fields as item, i}
|
||||
<FinancialChart
|
||||
data={financialData}
|
||||
{statementConfig}
|
||||
displayStatement={item?.key}
|
||||
filterRule={$selectedTimePeriod}
|
||||
{processedData}
|
||||
color={["#ff00cc", "#37ff00", "#0c63e7", "#07c8f9"][
|
||||
i % 4
|
||||
]}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="w-full rounded-none sm:rounded-md m-auto overflow-x-auto no-scrollbar"
|
||||
>
|
||||
<table
|
||||
class="table table-sm table-compact no-scrollbar rounded-none sm:rounded-md w-full border border-gray-300 dark:border-gray-800 m-auto"
|
||||
>
|
||||
<thead class="text-muted dark:text-white dark:bg-default">
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold pr-10"
|
||||
>Fiscal Year</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
{#if $selectedTimePeriod === "annual"}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{"FY" + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{:else}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end"
|
||||
>
|
||||
{item?.period + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold"
|
||||
>Period Ending</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
<td
|
||||
class=" font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{new Date(item?.date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row -->
|
||||
<FinancialTable data={financialData} {fields} />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<FinancialSection {data} title="Cash Flow Statement" {statementConfig} />
|
||||
|
||||
@ -1,28 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
coolMode,
|
||||
timeFrame,
|
||||
selectedTimePeriod,
|
||||
} from "$lib/store";
|
||||
import { removeCompanyStrings } from "$lib/utils";
|
||||
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||
//import * as XLSX from 'xlsx';
|
||||
import FinancialTable from "$lib/components/FinancialTable.svelte";
|
||||
import FinancialChart from "$lib/components/FinancialChart.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { displayCompanyName, stockTicker } from "$lib/store";
|
||||
|
||||
import SEO from "$lib/components/SEO.svelte";
|
||||
import FinancialSection from "$lib/components/FinancialSection.svelte";
|
||||
|
||||
export let data;
|
||||
|
||||
let tableList = [];
|
||||
let processedData = {};
|
||||
|
||||
let financialData = [];
|
||||
let fullStatement = [];
|
||||
|
||||
const statementConfig = [
|
||||
{
|
||||
propertyName: "priceToEarningsRatio",
|
||||
@ -140,380 +123,11 @@
|
||||
text: "EBITDA margin is the percentage of revenue left as EBITDA, after subtracting all expenses except interest, taxes, depreciation and amortization from revenue.",
|
||||
},
|
||||
];
|
||||
|
||||
const fields = statementConfig.map((item) => ({
|
||||
label: item.label,
|
||||
key: item.propertyName,
|
||||
}));
|
||||
function toggleMode() {
|
||||
$coolMode = !$coolMode;
|
||||
}
|
||||
|
||||
const getCurrentYear = () => new Date()?.getFullYear();
|
||||
|
||||
const filterStatement = (fullStatement, timeFrame) => {
|
||||
const currentYear = getCurrentYear();
|
||||
|
||||
switch (timeFrame) {
|
||||
case "5Y":
|
||||
return fullStatement?.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 5,
|
||||
);
|
||||
case "10Y":
|
||||
return fullStatement?.filter(
|
||||
(item) => currentYear - parseInt(item?.fiscalYear) < 10,
|
||||
);
|
||||
default:
|
||||
return fullStatement;
|
||||
}
|
||||
};
|
||||
|
||||
fullStatement = data?.getData;
|
||||
|
||||
const exportFundamentalData = (format = "csv") => {
|
||||
if (["Pro", "Plus"]?.includes(data?.user?.tier)) {
|
||||
const data = fullStatement;
|
||||
if (!data || data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let properties = [
|
||||
{
|
||||
key: $selectedTimePeriod === "annual" ? "fiscalYear" : "date",
|
||||
label: $selectedTimePeriod === "annual" ? "Year" : "Quarter",
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < statementConfig?.length; i++) {
|
||||
properties.push({
|
||||
key: statementConfig[i]?.propertyName,
|
||||
label: statementConfig[i]?.label,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to handle special cases
|
||||
|
||||
// Create rows for CSV/Excel
|
||||
let rows = data.map((item) =>
|
||||
properties?.map((property) => item[property?.key] || 0),
|
||||
);
|
||||
|
||||
// Include headers
|
||||
const headers = properties.map((prop) => prop.label);
|
||||
rows.unshift(headers);
|
||||
|
||||
// Check the format to export
|
||||
if (format.toLowerCase() === "csv") {
|
||||
const csvContent = rows.map((row) => row.join(",")).join("\n");
|
||||
const blob = new Blob([csvContent], {
|
||||
type: "data:text/csv;charset=utf-8",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download =
|
||||
$stockTicker.toLowerCase() +
|
||||
`${$selectedTimePeriod === "annual" ? "_annual" : $selectedTimePeriod === "quarterly" ? "_quarter" : "_ttm"}_ratios.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
} else {
|
||||
goto("/pricing");
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-process all data once instead of in each component
|
||||
function preprocessFinancialData() {
|
||||
processedData = {};
|
||||
|
||||
// Precompute mapping from propertyName to config for quick lookup
|
||||
const configMap = {};
|
||||
statementConfig.forEach((item) => {
|
||||
if (item && item.propertyName) {
|
||||
configMap[item.propertyName] = item;
|
||||
}
|
||||
});
|
||||
|
||||
// Precompute xList from income (reverse order)
|
||||
const xList = [];
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const year = statement.fiscalYear.slice(-2);
|
||||
const quarter = statement.period;
|
||||
xList.push(
|
||||
$selectedTimePeriod === "annual"
|
||||
? "FY" + year
|
||||
: "FY" + year + " " + quarter,
|
||||
);
|
||||
}
|
||||
|
||||
// Process each field using precomputed config and xList
|
||||
fields.forEach((field) => {
|
||||
const statementKey = field.key;
|
||||
const config = configMap[statementKey];
|
||||
if (!config) return;
|
||||
|
||||
const valueList = [];
|
||||
// Loop through financialData in reverse to match xList order
|
||||
for (let i = financialData.length - 1; i >= 0; i--) {
|
||||
const statement = financialData[i];
|
||||
const rawValue = Number(statement[config.propertyName]);
|
||||
// Round to two decimals
|
||||
const value = parseFloat(rawValue.toFixed(2));
|
||||
valueList.push(value);
|
||||
}
|
||||
|
||||
processedData[statementKey] = {
|
||||
xList, // re-use the precomputed labels
|
||||
valueList,
|
||||
labelName: config.label,
|
||||
};
|
||||
});
|
||||
|
||||
// Build tableList once for all charts and sort by date (newest first)
|
||||
tableList = financialData?.map((statement) => ({
|
||||
date: statement.date,
|
||||
// Add more properties if needed
|
||||
}));
|
||||
|
||||
tableList.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($timeFrame || $selectedTimePeriod) {
|
||||
if ($selectedTimePeriod === "annual") {
|
||||
fullStatement = data?.getData?.annual;
|
||||
} else if ($selectedTimePeriod === "quarterly") {
|
||||
fullStatement = data?.getData?.quarter;
|
||||
} else if ($selectedTimePeriod === "ttm") {
|
||||
fullStatement = data?.getData?.ttm;
|
||||
} else {
|
||||
fullStatement = data?.getData?.annual;
|
||||
}
|
||||
|
||||
financialData = filterStatement(fullStatement, $timeFrame);
|
||||
preprocessFinancialData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SEO
|
||||
title={`${$displayCompanyName} (${$stockTicker}) Ratios Statement`}
|
||||
title={`${$displayCompanyName} (${$stockTicker}) Ratios and Metrics Statement`}
|
||||
description={`Financial ratios and valuation metrics for ${$displayCompanyName} (${$stockTicker}). Includes annual, quarterly and trailing numbers with history and charts.`}
|
||||
/>
|
||||
|
||||
<section class=" w-full overflow-hidden h-full">
|
||||
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
|
||||
<div
|
||||
class="w-full relative flex justify-center items-center overflow-hidden"
|
||||
>
|
||||
<main class="w-full">
|
||||
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
|
||||
<div
|
||||
class="mb-3 sm:mb-0 flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
||||
>
|
||||
<h1 class="text-xl sm:text-2xl font-bold">
|
||||
{removeCompanyStrings($displayCompanyName)} Income Statement
|
||||
</h1>
|
||||
<label class="inline-flex sm:hidden mt-4 cursor-pointer relative">
|
||||
<input
|
||||
on:click={toggleMode}
|
||||
type="checkbox"
|
||||
checked={$coolMode}
|
||||
value={$coolMode}
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-400 rounded-full peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#1563F9]"
|
||||
></div>
|
||||
{#if $coolMode}
|
||||
<span class="ml-2 text-sm"> Table Mode </span>
|
||||
{:else}
|
||||
<span class="ml-2 text-sm"> Chart Mode </span>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
{#if financialData?.length > 0}
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-start sm:items-end sm:justify-between"
|
||||
>
|
||||
<span
|
||||
class="text-xs sm:text-sm order-1 sm:order-0 mt-5 sm:mt-0 text-gray-600 dark:text-gray-400 w-full"
|
||||
>
|
||||
Financials in {financialData?.at(0)?.reportedCurrency}. Fiscal
|
||||
year is
|
||||
{data?.getProfileData?.fiscalYearRange}.
|
||||
</span>
|
||||
<div class="flex flex-row items-center justify-end w-full">
|
||||
<label
|
||||
class="hidden sm:inline-flex ml-auto mt-2 sm:mt-0 cursor-pointer relative"
|
||||
>
|
||||
<input
|
||||
on:click={toggleMode}
|
||||
type="checkbox"
|
||||
checked={$coolMode}
|
||||
value={$coolMode}
|
||||
class="sr-only peer"
|
||||
/>
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-400 rounded-full peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#1563F9]"
|
||||
></div>
|
||||
{#if $coolMode}
|
||||
<span class="ml-2 text-sm"> Table Mode </span>
|
||||
{:else}
|
||||
<span class="ml-2 text-sm"> Chart Mode </span>
|
||||
{/if}
|
||||
</label>
|
||||
<div class="ml-auto sm:ml-5 relative inline-block">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
builders={[builder]}
|
||||
class="shadow-sm w-full sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<span class="truncate">{$timeFrame}</span>
|
||||
<svg
|
||||
class="-mr-1 ml-1 h-5 w-5 xs:ml-2 inline-block"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
style="max-width:40px"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-56 h-fit max-h-72 overflow-y-auto scroller"
|
||||
>
|
||||
<DropdownMenu.Label
|
||||
class="text-muted dark:text-gray-400 font-normal"
|
||||
>
|
||||
Select time frame
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "5Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
5 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "10Y")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
10 years
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
on:click={() => ($timeFrame = "MAX")}
|
||||
class="cursor-pointer sm:hover:bg-gray-300 dark:sm:hover:bg-primary"
|
||||
>
|
||||
Max
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
on:click={() => exportFundamentalData("csv")}
|
||||
class="shadow-sm ml-2 w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-100 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-1.5 rounded-md truncate"
|
||||
>
|
||||
<span class="truncate">Download</span>
|
||||
<svg
|
||||
class="{['Pro', 'Plus']?.includes(data?.user?.tier)
|
||||
? 'hidden'
|
||||
: ''} ml-1 -mt-0.5 w-3.5 h-3.5"
|
||||
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
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if $coolMode}
|
||||
<div class="grid gap-5 xs:gap-6 lg:grid-cols-3 lg:gap-3">
|
||||
{#each fields as item, i}
|
||||
<FinancialChart
|
||||
data={financialData}
|
||||
{statementConfig}
|
||||
displayStatement={item?.key}
|
||||
filterRule={$selectedTimePeriod}
|
||||
{processedData}
|
||||
color={["#ff00cc", "#37ff00", "#0c63e7", "#07c8f9"][
|
||||
i % 4
|
||||
]}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="w-full rounded-none sm:rounded-md m-auto overflow-x-auto no-scrollbar"
|
||||
>
|
||||
<table
|
||||
class="table table-sm table-compact no-scrollbar rounded-none sm:rounded-md w-full border border-gray-300 dark:border-gray-800 m-auto"
|
||||
>
|
||||
<thead class="text-muted dark:text-white dark:bg-default">
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold pr-10"
|
||||
>Fiscal Year</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
{#if $selectedTimePeriod === "annual"}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{"FY" + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{:else}
|
||||
<td
|
||||
class="min-w-[130px] font-semibold text-sm text-end"
|
||||
>
|
||||
{item?.period + " " + item?.fiscalYear}
|
||||
</td>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
<tr class="border min-w-[250px]">
|
||||
<td class="text-start text-sm font-semibold"
|
||||
>Period Ending</td
|
||||
>
|
||||
{#each financialData as item}
|
||||
<td
|
||||
class=" font-semibold text-sm text-end border-l border-gray-300 dark:border-gray-800"
|
||||
>
|
||||
{new Date(item?.date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row -->
|
||||
<FinancialTable data={financialData} {fields} />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<FinancialSection {data} title="Ratios and Metrics" {statementConfig} />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user