update stock list page

This commit is contained in:
MuslemRahimi 2024-11-12 01:30:28 +01:00
parent 8fbff43eb2
commit 093ff73a6d
3 changed files with 169 additions and 237 deletions

View File

@ -1,11 +1,14 @@
<script lang="ts"> <script lang="ts">
import { goto } from "$app/navigation";
import { screenWidth } from "$lib/store"; import { screenWidth } from "$lib/store";
import { abbreviateNumber } from "$lib/utils"; import { abbreviateNumber } from "$lib/utils";
import TableHeader from "$lib/components/Table/TableHeader.svelte";
import HoverStockChart from "$lib/components/HoverStockChart.svelte";
import InfoModal from "$lib/components/InfoModal.svelte"; import InfoModal from "$lib/components/InfoModal.svelte";
import DownloadData from "$lib/components/DownloadData.svelte";
export let data; export let data;
let rawData = data?.getMagnificentSeven; let rawData = data?.getMagnificentSeven;
let stockList = rawData;
let totalMarketCap = let totalMarketCap =
rawData?.reduce((total, stock) => total + stock?.marketCap, 0) ?? 0; rawData?.reduce((total, stock) => total + stock?.marketCap, 0) ?? 0;
@ -18,12 +21,91 @@
rawData?.length rawData?.length
)?.toFixed(2); )?.toFixed(2);
let columns = [
{ key: "rank", label: "Rank", align: "center" },
{ key: "symbol", label: "Symbol", align: "left" },
{ key: "name", label: "Name", align: "left" },
{ key: "price", label: "Price", align: "right" },
{ key: "changesPercentage", label: "% Change", align: "right" },
{ key: "marketCap", label: "Market Cap", align: "right" },
{ key: "revenue", label: "Revenue", align: "right" },
];
let sortOrders = {
rank: { order: "none", type: "number" },
symbol: { order: "none", type: "string" },
name: { order: "none", type: "string" },
price: { order: "none", type: "number" },
changesPercentage: { order: "none", type: "number" },
marketCap: { order: "none", type: "number" },
revenue: { order: "none", type: "number" },
};
const sortData = (key) => {
// Reset all other keys to 'none' except the current key
for (const k in sortOrders) {
if (k !== key) {
sortOrders[k].order = "none";
}
}
// Cycle through 'none', 'asc', 'desc' for the clicked key
const orderCycle = ["none", "asc", "desc"];
let originalData = rawData;
const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order);
sortOrders[key].order =
orderCycle[(currentOrderIndex + 1) % orderCycle.length];
const sortOrder = sortOrders[key].order;
// Reset to original data when 'none' and stop further sorting
if (sortOrder === "none") {
stockList = [...originalData]; // Reset to original data (spread to avoid mutation)
return;
}
// Define a generic comparison function
const compareValues = (a, b) => {
const { type } = sortOrders[key];
let valueA, valueB;
switch (type) {
case "date":
valueA = new Date(a[key]);
valueB = new Date(b[key]);
break;
case "string":
valueA = a[key].toUpperCase();
valueB = b[key].toUpperCase();
return sortOrder === "asc"
? valueA.localeCompare(valueB)
: valueB.localeCompare(valueA);
case "number":
default:
valueA = parseFloat(a[key]);
valueB = parseFloat(b[key]);
break;
}
if (sortOrder === "asc") {
return valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
} else {
return valueA > valueB ? -1 : valueA < valueB ? 1 : 0;
}
};
// Sort using the generic comparison function
stockList = [...originalData].sort(compareValues);
};
$: charNumber = $screenWidth < 640 ? 15 : 20; $: charNumber = $screenWidth < 640 ? 15 : 20;
</script> </script>
<section class="w-full overflow-hidden m-auto"> <section class="w-full overflow-hidden m-auto">
<div <div
class="w-full border border-gray-800 sm:flex sm:flex-row sm:items-center m-auto text-gray-100 bg-[#09090B] sm:rounded-lg h-auto p-5 mb-4" class="w-full border border-gray-600 sm:flex sm:flex-row sm:items-center m-auto text-white bg-[#09090B] sm:rounded-lg h-auto p-5 mb-4"
> >
<svg <svg
class="w-5 h-5 inline-block sm:mr-2 flex-shrink-0" class="w-5 h-5 inline-block sm:mr-2 flex-shrink-0"
@ -35,128 +117,48 @@
/></svg /></svg
> >
The term "Magnificent 7" refers to a group of 7 well-known technological The "magnificent seven" stocks are 7 technology stocks that drove a large
stocks that made large contributions to the market's overall performance. portion of the market's returns in 2023 and 2024. The list includes Apple,
Microsoft, Amazon, Alphabet (Google), Tesla, Nvidia and Meta Platforms.
</div> </div>
<div class="stats stats-horizontal bg-[#27272A] w-full rounded-lg"> <div
<div class="grid grid-cols-2 sm:grid-cols-3"> class="mb-4 flex flex-col divide-y divide-gray-600 rounded-md border border-gray-600 sm:grid sm:grid-cols-3 sm:divide-x sm:divide-y-0"
<div class="stat">
<div class="flex flex-row items-center">
<label
for="stocksInfo"
class="cursor-pointer stat-title text-md sm:text-lg font-semibold text-gray-300"
> >
Stocks <div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
</label> <div class="flex items-center justify-between sm:block">
<InfoModal <div class="text-sm font-normal text-white">Total Stocks</div>
title={"Stocks"} <div
content={"The total number of companies who operates in this sector."} class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
id={"stocksInfo"}
/>
</div>
<div class="stat-value mt-1 text-lg font-semibold text-gray-200">
{rawData?.length}
</div>
</div>
<div class="stat">
<div class="flex flex-row items-center">
<label
for="marketCapModal"
class="cursor-pointer stat-title text-md sm:text-lg font-semibold text-gray-300"
> >
Market Cap {new Intl.NumberFormat("en")?.format(rawData?.length)}
</label>
<InfoModal
title={"Market Cap"}
content={"Combined market cap of all companies in this group."}
id={"marketCapModal"}
/>
</div>
<div class="stat-value mt-1 text-lg font-semibold text-gray-200">
{abbreviateNumber(totalMarketCap, true)}
</div> </div>
</div> </div>
</div>
<div class="stat"> <div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex flex-row items-center"> <div class="flex items-center justify-between sm:block">
<label <div class="text-sm font-normal text-white">Total Market Cap</div>
for="revenueInfo" <div
class="cursor-pointer stat-title text-md sm:text-lg font-semibold text-gray-300" class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
> >
Revenue {abbreviateNumber(totalMarketCap)}
</label>
<InfoModal
title={"Revenue"}
content={"The total revenue of all companies in this group"}
id={"revenueInfo"}
/>
</div>
<div class="stat-value text-lg font-semibold text-gray-200">
{abbreviateNumber(totalRevenue, true)}
</div> </div>
</div> </div>
</div>
<div class="stat"> <div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex flex-row items-center"> <div class="flex items-center justify-between sm:block">
<label <div class="text-sm font-normal text-white">Total Revenue</div>
for="profitsInfo" <div
class="cursor-pointer stat-title text-md sm:text-lg font-semibold text-gray-300" class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
> >
Profits {abbreviateNumber(totalRevenue)}
</label> </div>
<InfoModal
title={"Profits"}
content={"The total net income of all companies in this group"}
id={"profitsInfo"}
/>
</div> </div>
<div class="stat-value text-lg font-semibold text-gray-200">
{abbreviateNumber(totalProfits, true)}
</div> </div>
</div> </div>
<div class="stat"> <div class="flex flex-row items-end justify-end w-fit ml-auto mt-5 mb-2">
<div class="flex flex-row items-center"> <DownloadData {data} {rawData} title="magnificent-seven" />
<label
for="profitMarginInfo"
class="cursor-pointer stat-title text-md sm:text-lg font-semibold text-gray-300"
>
Profit Margin
</label>
<InfoModal
title={"Profit Margin"}
content={"The magnificent seven profit margin, calculated by dividing the total net income of all constituents by the total revenue."}
id={"profitMarginInfo"}
/>
</div>
<div class="stat-value text-lg font-semibold text-gray-200">
{((totalProfits / totalRevenue) * 100)?.toFixed(2)}%
</div>
</div>
<div class="stat">
<div class="flex flex-row items-center">
<label
for="peRatioInfo"
class="cursor-pointer stat-title text-md sm:text-lg font-semibold text-gray-300"
>
Avg. PE Ratio
</label>
<InfoModal
title={"PE Ratio"}
content={"The magnificent seven average PE Ratio, calculated by dividing total PE Ratio of all constituents by the number of constituents."}
id={"peRatioInfo"}
/>
</div>
<div class="stat-value text-lg font-semibold text-gray-200">
{avgPERatio}
</div>
</div>
</div>
</div> </div>
<!-- Page wrapper --> <!-- Page wrapper -->
@ -167,32 +169,24 @@
class="table table-sm sm:table-md table-compact rounded-none sm:rounded-md w-full border-bg-[#09090B] m-auto mt-4" class="table table-sm sm:table-md table-compact rounded-none sm:rounded-md w-full border-bg-[#09090B] m-auto mt-4"
> >
<thead> <thead>
<tr class="border border-slate-800"> <TableHeader {columns} {sortOrders} {sortData} />
<th class="text-white font-semibold text-[1rem]">Symbol</th>
<th class="text-white font-semibold text-[1rem]">Company</th>
<th class="text-white font-semibold text-end text-[1rem]"
>Market Cap</th
>
<th class="text-white font-semibold text-center text-[1rem]"
>Revenue</th
>
<th class="text-white font-semibold text-center text-[1rem]"
>Profits</th
>
<th class="text-white font-semibold text-[1rem] text-end">Price</th>
</tr>
</thead> </thead>
<tbody> <tbody>
{#each rawData as item} {#each stockList as item}
<!-- row --> <!-- row -->
<tr <tr
on:click={() => goto("/stocks/" + item?.symbol)} class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-[#27272A] border-b-[#09090B] text-white"
class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-[#27272A] border-b-[#09090B] shake-ticker cursor-pointer"
> >
<td
class="text-center text-sm sm:text-[1rem] whitespace-nowrap font-medium border-b-[#09090B]"
>
{item?.rank}
</td>
<td <td
class="text-blue-400 text-sm sm:text-[1rem] whitespace-nowrap font-medium border-b-[#09090B]" class="text-blue-400 text-sm sm:text-[1rem] whitespace-nowrap font-medium border-b-[#09090B]"
> >
{item?.symbol} <HoverStockChart symbol={item?.symbol} />
</td> </td>
<td <td
@ -206,74 +200,34 @@
<td <td
class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-end border-b-[#09090B]" class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-end border-b-[#09090B]"
> >
{abbreviateNumber(item?.marketCap, true)} {item.price?.toFixed(2)}
</td> </td>
<td <td
class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-center border-b-[#09090B]" class="text-white text-end text-sm sm:text-[1rem] border-b-[#09090B]"
>
{item?.revenue !== null
? abbreviateNumber(item?.revenue, true)
: "-"}
</td>
<td
class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-center border-b-[#09090B]"
>
{item?.netIncome !== null
? abbreviateNumber(item?.netIncome, true)
: "-"}
</td>
<td
class="text-white text-sm sm:text-[1rem] whitespace-nowrap border-b-[#09090B]"
>
<div class="flex flex-row justify-end items-center">
<div class="flex flex-col">
<span class="text-white font-semibold text-md ml-auto"
>${item.price?.toFixed(2)}</span
>
<div class="flex flex-row mt-0.5 ml-auto">
{#if item.changesPercentage >= 0}
<svg
class="w-5 h-5 -mr-0.5 -mt-0.5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><g id="evaArrowUpFill0"
><g id="evaArrowUpFill1"
><path
id="evaArrowUpFill2"
fill="#00FC50"
d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"
/></g
></g
></svg
> >
{#if item?.changesPercentage >= 0}
<span class="text-[#00FC50]" <span class="text-[#00FC50]"
>+{item.changesPercentage?.toFixed(2)}%</span >+{item.changesPercentage?.toFixed(2)}%</span
> >
{:else} {:else}
<svg
class="w-5 h-5 -mr-0.5 -mt-0.5 rotate-180"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><g id="evaArrowUpFill0"
><g id="evaArrowUpFill1"
><path
id="evaArrowUpFill2"
fill="#FF2F1F"
d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"
/></g
></g
></svg
>
<span class="text-[#FF2F1F]" <span class="text-[#FF2F1F]"
>{item.changesPercentage?.toFixed(2)}% >{item.changesPercentage?.toFixed(2)}%
</span> </span>
{/if} {/if}
</div> </td>
</div>
</div> <td
class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-end border-b-[#09090B]"
>
{item?.marketCap !== null
? abbreviateNumber(item?.marketCap)
: "-"}
</td>
<td
class="text-white text-sm sm:text-[1rem] whitespace-nowrap font-medium text-end border-b-[#09090B]"
>
{item?.revenue !== null ? abbreviateNumber(item?.revenue) : "-"}
</td> </td>
</tr> </tr>
{/each} {/each}

View File

@ -5,6 +5,7 @@
import { page } from "$app/stores"; import { page } from "$app/stores";
import HoverStockChart from "$lib/components/HoverStockChart.svelte"; import HoverStockChart from "$lib/components/HoverStockChart.svelte";
import TableHeader from "$lib/components/Table/TableHeader.svelte"; import TableHeader from "$lib/components/Table/TableHeader.svelte";
import DownloadData from "$lib/components/DownloadData.svelte";
export let data; export let data;
@ -270,6 +271,14 @@
</div> </div>
</div> </div>
<div class="flex flex-row items-end justify-end w-fit ml-auto mt-5 mb-2">
<DownloadData
{data}
rawData={data?.getMarketCapCategory}
title={data?.getParams}
/>
</div>
<!-- Page wrapper --> <!-- Page wrapper -->
<div class="flex justify-center w-full m-auto h-full overflow-hidden"> <div class="flex justify-center w-full m-auto h-full overflow-hidden">
<!-- Content area --> <!-- Content area -->

View File

@ -4,6 +4,7 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import { page } from "$app/stores"; import { page } from "$app/stores";
import HoverStockChart from "$lib/components/HoverStockChart.svelte"; import HoverStockChart from "$lib/components/HoverStockChart.svelte";
import DownloadData from "$lib/components/DownloadData.svelte";
import TableHeader from "$lib/components/Table/TableHeader.svelte"; import TableHeader from "$lib/components/Table/TableHeader.svelte";
@ -12,46 +13,6 @@
let rawData = []; let rawData = [];
let marketCapList = []; let marketCapList = [];
const BILLION = 1_000_000_000;
const MILLION = 1_000_000;
const marketCapNavigation = [
{
threshold: 200 * BILLION,
name: "Mega Cap",
link: "/list/market-cap/mega-cap-stocks",
},
{
minThreshold: 10 * BILLION,
maxThreshold: 200 * BILLION,
name: "Large Cap",
link: "/list/market-cap/large-cap-stocks",
},
{
minThreshold: 2 * BILLION,
maxThreshold: 10 * BILLION,
name: "Mid Cap",
link: "/list/market-cap/mid-cap-stocks",
},
{
minThreshold: 300 * MILLION,
maxThreshold: 2 * BILLION,
name: "Small Cap",
link: "/list/market-cap/small-cap-stocks",
},
{
minThreshold: 50 * MILLION,
maxThreshold: 300 * MILLION,
name: "Micro Cap",
link: "/list/market-cap/micro-cap-stocks",
},
{
maxThreshold: 50 * MILLION,
name: "Nano Cap",
link: "/list/market-cap/nano-cap-stocks",
},
];
async function handleScroll() { async function handleScroll() {
const scrollThreshold = document.body.offsetHeight * 0.8; // 80% of the website height const scrollThreshold = document.body.offsetHeight * 0.8; // 80% of the website height
const isBottom = window.innerHeight + window.scrollY >= scrollThreshold; const isBottom = window.innerHeight + window.scrollY >= scrollThreshold;
@ -216,6 +177,14 @@
</div> </div>
</div> </div>
<div class="flex flex-row items-end justify-end w-fit ml-auto mt-5 mb-2">
<DownloadData
{data}
rawData={data?.getSectorCategory}
title={data?.getParams}
/>
</div>
<!-- Page wrapper --> <!-- Page wrapper -->
<div class="flex justify-center w-full m-auto h-full overflow-hidden"> <div class="flex justify-center w-full m-auto h-full overflow-hidden">
<!-- Content area --> <!-- Content area -->