make stock screener table sortable

This commit is contained in:
MuslemRahimi 2024-11-08 08:45:21 +01:00
parent 5c418c5952
commit 2554c5b398

View File

@ -18,6 +18,7 @@
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
import { Button } from "$lib/components/shadcn/button/index.js";
import * as HoverCard from "$lib/components/shadcn/hover-card/index.js";
import TableHeader from "$lib/components/Table/TableHeader.svelte";
import Input from "$lib/components/Input.svelte";
@ -1726,48 +1727,6 @@ const handleKeyDown = (event) => {
}
}
enum Order {
HighToLow = "highToLow",
LowToHigh = "lowToHigh",
}
enum SortBy {
Change = "change",
MarketCap = "marketCap",
PE = "pe", // Add new sorting criteria here
Volume = "volume", // Add new sorting criteria here
}
// Mapping of SortBy enum to actual data keys
const sortByKeys: Record<SortBy, string> = {
[SortBy.Change]: "changesPercentage",
[SortBy.MarketCap]: "marketCap",
[SortBy.PE]: "pe",
[SortBy.Volume]: "volume", // Add new key here
};
let order = Order.HighToLow;
let sortBy = SortBy.MarketCap; // Default sorting by change percentage
function changeOrder(state: Order) {
order = state === Order.HighToLow ? Order.LowToHigh : Order.HighToLow;
}
const sortItems = (tickerList: any[], key: string) => {
return tickerList?.sort((a, b) => {
const aValue = a[key] ?? 0;
const bValue = b[key] ?? 0;
return order === Order.HighToLow ? bValue - aValue : aValue - bValue;
});
};
$: {
if (order) {
const key = sortByKeys[sortBy]; // Use the mapping to get the key
displayResults = sortItems(filteredData, key)?.slice(0, 50);
}
}
$: {
if (searchTerm) {
filteredRows = allRows?.filter((row) =>
@ -1931,14 +1890,6 @@ const handleKeyDown = (event) => {
{ condition: "over", name: "freeCashFlowMargin", value: "50%" },
],
},
bestHalalStocks: {
// New Strategy Added
name: "Top Halal Stocks",
rules: [
{ condition: "", name: "halalStocks", value: "Compliant" },
{ condition: "over", name: "marketCap", value: "1B" },
],
},
};
const strategy = strategies[state];
@ -1981,6 +1932,140 @@ const handleKeyDown = (event) => {
}
}, 50);
}
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 = filteredData;
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") {
displayResults = [...originalData]?.slice(0, 50); // 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
displayResults = [...originalData].sort(compareValues)?.slice(0, 50);
};
let columns;
let sortOrders;
// Initial columns and sort orders for the "general" tab
const generalColumns = [
{ key: "symbol", label: "Symbol", align: "left" },
{ key: "name", label: "Name", align: "left" },
{ key: "marketCap", label: "Market Cap", align: "right" },
{ key: "changesPercentage", label: "% Change", align: "right" },
{ key: "price", label: "Price", align: "right" },
{ key: "volume", label: "Volume", align: "right" },
{ key: "pe", label: "PE Ratio", align: "right" },
];
const generalSortOrders = {
symbol: { order: "none", type: "string" },
name: { order: "none", type: "string" },
marketCap: { order: "none", type: "number" },
changesPercentage: { order: "none", type: "number" },
price: { order: "none", type: "number" },
volume: { order: "none", type: "number" },
pe: { order: "none", type: "number" },
};
// Initial columns and sort orders for other tabs
const baseColumns = [
{ key: "symbol", label: "Symbol", align: "left" },
{ key: "name", label: "Name", align: "left" },
{ key: "marketCap", label: "Market Cap", align: "right" },
];
const baseSortOrders = {
symbol: { order: "none", type: "string" },
name: { order: "none", type: "string" },
marketCap: { order: "none", type: "number" },
};
const stringTypeRules = [
"country",
"industry",
"score",
"sector",
"analystRating",
"halalStocks",
];
// Helper to determine the type based on stringTypeRules
const getType = (key) =>
stringTypeRules.includes(key) ? "string" : "number";
$: {
if (displayTableTab) {
if (displayTableTab === "general") {
// Set columns and sortOrders for the "general" tab
columns = [...generalColumns];
sortOrders = { ...generalSortOrders };
} else {
// Set base columns and sortOrders, then extend with displayRules
columns = [...baseColumns];
sortOrders = { ...baseSortOrders };
displayRules?.forEach((rule) => {
if (rule.rule !== "marketCap") {
// Ignore "marketCap" if it's present in displayRules
columns.push({
key: rule.rule,
label: rule.label,
align: "right",
});
sortOrders[rule.rule] = { order: "none", type: getType(rule.rule) };
}
});
}
}
}
</script>
<svelte:head>
@ -2125,12 +2210,6 @@ const handleKeyDown = (event) => {
>
Strong Cash Flow
</DropdownMenu.Item>
<DropdownMenu.Item
on:click={() => popularStrategy("bestHalalStocks")}
class="cursor-pointer hover:bg-[#27272A]"
>
Top Halal Stocks
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
@ -2772,113 +2851,7 @@ const handleKeyDown = (event) => {
class="table table-sm table-compact w-full bg-[#09090B] border-bg-[#09090B]"
>
<thead>
<tr class="border-b-[#1A1A27]">
<th
class="text-white bg-[#09090B] text-sm sm:text-[1rem] font-semibold border-b-[#09090B]"
>Symbol</th
>
<th
class="text-white hidden sm:table-cell bg-[#09090B] text-sm sm:text-[1rem] font-semibold border-b-[#09090B]"
>Company Name</th
>
<th
on:click={() => {
sortBy = "marketCap";
changeOrder(order);
}}
class="whitespace-nowrap cursor-pointer text-white font-semibold text-sm sm:text-[1rem] font-semibold text-end"
>
Market Cap
<svg
class="w-5 h-5 inline-block {order === 'highToLow' &&
sortBy === 'marketCap'
? ''
: 'rotate-180'}"
viewBox="0 0 20 20"
fill="currentColor"
style="max-width:40px"
><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
>
</th>
<th
on:click={() => {
sortBy = "change";
changeOrder(order);
}}
class="whitespace-nowrap cursor-pointer text-white font-semibold text-sm sm:text-[1rem] font-semibold text-end"
>
% Change
<svg
class="w-5 h-5 inline-block {order === 'highToLow' &&
sortBy === 'change'
? 'rotate-180'
: ''}"
viewBox="0 0 20 20"
fill="currentColor"
style="max-width:40px"
><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
>
</th>
<th
class="text-white bg-[#09090B] text-end text-sm sm:text-[1rem] font-semibold border-b-[#09090B]"
>
Price
</th>
<th
on:click={() => {
sortBy = "volume";
changeOrder(order);
}}
class="whitespace-nowrap cursor-pointer text-white font-semibold text-sm sm:text-[1rem] font-semibold text-end"
>
Volume
<svg
class="w-5 h-5 inline-block {order === 'highToLow' &&
sortBy === 'volume'
? 'rotate-180'
: ''}"
viewBox="0 0 20 20"
fill="currentColor"
style="max-width:40px"
><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
>
</th>
<th
on:click={() => {
sortBy = "pe";
changeOrder(order);
}}
class="whitespace-nowrap cursor-pointer text-white font-semibold text-sm sm:text-[1rem] font-semibold text-end"
>
PE Ratio
<svg
class="w-5 h-5 inline-block {order === 'highToLow' &&
sortBy === 'pe'
? 'rotate-180'
: ''}"
viewBox="0 0 20 20"
fill="currentColor"
style="max-width:40px"
><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
>
</th>
</tr>
<TableHeader {columns} {sortOrders} {sortData} />
</thead>
<tbody>
{#each displayResults as item}
@ -2963,31 +2936,7 @@ const handleKeyDown = (event) => {
class="table table-sm table-compact w-full bg-[#09090B] border-bg-[#09090B]"
>
<thead>
<tr class="border-b-[#1A1A27]">
<th
class="text-white bg-[#09090B] text-sm sm:text-[1rem] font-semibold border-b-[#09090B]"
>Symbol</th
>
<th
class="text-white hidden sm:table-cell bg-[#09090B] text-sm sm:text-[1rem] font-semibold border-b-[#09090B]"
>Company Name</th
>
<th
class="text-white text-end bg-[#09090B] text-sm sm:text-[1rem] font-semibold border-b-[#09090B]"
>Market Cap</th
>
{#each displayRules as row (row?.rule)}
{#if row?.rule !== "marketCap"}
<th
class="text-white text-end bg-[#09090B] text-sm sm:text-[1rem] font-semibold border-b-[#09090B]"
>
{row?.label?.length > 20
? row?.label?.slice(0, 20) + "..."
: row?.label}
</th>
{/if}
{/each}
</tr>
<TableHeader {columns} {sortOrders} {sortData} />
</thead>
<tbody>
{#each displayResults as item (item?.symbol)}