diff --git a/src/routes/sentiment-tracker/+page.svelte b/src/routes/sentiment-tracker/+page.svelte index 2ab43795..68d87a1b 100644 --- a/src/routes/sentiment-tracker/+page.svelte +++ b/src/routes/sentiment-tracker/+page.svelte @@ -140,7 +140,7 @@ class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer bg-primary sm:hover:bg-secondary transition ease-out duration-100" >
diff --git a/src/routes/stocks/[tickerID]/history/+page.svelte b/src/routes/stocks/[tickerID]/history/+page.svelte index c82b4d30..a07e7cc7 100644 --- a/src/routes/stocks/[tickerID]/history/+page.svelte +++ b/src/routes/stocks/[tickerID]/history/+page.svelte @@ -8,7 +8,8 @@ import Infobox from "$lib/components/Infobox.svelte"; import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js"; import { Button } from "$lib/components/shadcn/button/index.js"; - import UpgradeToPro from "$lib/components/UpgradeToPro.svelte"; + import { goto } from "$app/navigation"; + import ArrowLogo from "lucide-svelte/icons/move-up-right"; import { onMount } from "svelte"; @@ -20,50 +21,76 @@ let stockList = []; function prepareDataset(data, timePeriod = "Daily") { - if (timePeriod === "Weekly") { - // Group data by week - const weeklyData = []; - let currentWeek = null; + if ( + timePeriod === "Weekly" || + timePeriod === "Monthly" || + timePeriod === "Quarterly" || + timePeriod === "Annual" + ) { + // Group data by week, month, quarter, or year + const aggregatedData = []; + let currentPeriod = null; data.forEach((entry) => { const date = new Date(entry.time); - // Calculate the start of the week (Monday) - const dayOfWeek = date.getDay(); // 0 for Sunday, 1 for Monday, etc. - const weekStart = new Date( - date.setDate(date.getDate() - ((dayOfWeek + 6) % 7)), // Adjust to get Monday - ); + let periodStart; + let periodKey; - if ( - !currentWeek || - currentWeek.weekStart.getTime() !== weekStart.getTime() - ) { - // Start a new week - currentWeek = { - weekStart, + if (timePeriod === "Weekly") { + // Calculate the start of the week (Monday) + const dayOfWeek = date.getDay(); // 0 for Sunday, 1 for Monday, etc. + periodStart = new Date( + date.setDate(date.getDate() - ((dayOfWeek + 6) % 7)), // Adjust to get Monday + ); + periodKey = periodStart.getTime(); + } else if (timePeriod === "Monthly") { + // Use year and month as the period key + periodKey = `${date.getFullYear()}-${date.getMonth()}`; + // Get the last day of the month + periodStart = new Date(date.getFullYear(), date.getMonth() + 1, 0); + } else if (timePeriod === "Quarterly") { + // Calculate quarter (0-3) + const quarter = Math.floor(date.getMonth() / 3); + periodKey = `${date.getFullYear()}-Q${quarter}`; + // Get the last day of the quarter + const lastMonthOfQuarter = (quarter + 1) * 3 - 1; + periodStart = new Date(date.getFullYear(), lastMonthOfQuarter + 1, 0); + } else { + // Annual + periodKey = date.getFullYear().toString(); + // Get the last day of the year (December 31st) + periodStart = new Date(date.getFullYear(), 11, 31); + } + + if (!currentPeriod || currentPeriod.periodKey !== periodKey) { + // Start a new period + currentPeriod = { + periodStart, + periodKey, open: entry.open, high: entry.high, low: entry.low, close: entry.close, volume: entry.volume, }; - weeklyData.push(currentWeek); + aggregatedData.push(currentPeriod); } else { - // Update the current week's values - currentWeek.high = Math.max(currentWeek.high, entry.high); - currentWeek.low = Math.min(currentWeek.low, entry.low); - currentWeek.close = entry.close; // Update the close to the most recent in the week - currentWeek.volume += entry.volume; + // Update the current period's values + currentPeriod.high = Math.max(currentPeriod.high, entry.high); + currentPeriod.low = Math.min(currentPeriod.low, entry.low); + currentPeriod.close = entry.close; // Update the close to the most recent in the period + currentPeriod.volume += entry.volume; } }); - // Replace Daily data with aggregated weekly data - data = weeklyData.map((week) => ({ - time: week.weekStart.toISOString().split("T")[0], - open: week.open, - high: week.high, - low: week.low, - close: week.close, - volume: week.volume, + // Replace Daily data with aggregated data + data = aggregatedData.map((period) => ({ + time: period.periodStart.toISOString().split("T")[0], + open: period.open, + high: period.high, + low: period.low, + close: period.close, + volume: period.volume, })); } @@ -72,7 +99,6 @@ if (index === 0) { return { ...entry, change: null, changesPercentage: null }; } - const previousClose = arr[index - 1]?.close; const currentClose = entry?.close; const change = (currentClose - previousClose)?.toFixed(2); @@ -80,7 +106,6 @@ previousClose !== 0 ? (((currentClose - previousClose) / previousClose) * 100)?.toFixed(2) : null; - return { ...entry, change, changesPercentage }; }); @@ -185,12 +210,64 @@ stockList = [...originalData].sort(compareValues)?.slice(0, 50); }; + async function exportData() { + if (data?.user?.tier === "Pro") { + let exportList = rawData?.map( + ({ + time, + open, + high, + low, + close, + change, + changesPercentage, + volume, + }) => ({ + time, + open, + high, + low, + close, + change, + changesPercentage, + volume, + }), + ); + + const csvRows = []; + + // Add headers row + csvRows.push("time,open,high,low,close,change,changesPercentage,volume"); + + // Add data rows + for (const row of exportList) { + const csvRow = `${row.time},${row.open},${row.high},${row.low},${row.close},${row.change},${row.changesPercentage},${row.volume}`; + csvRows.push(csvRow); + } + + // Create CSV blob and trigger download + const csv = csvRows.join("\n"); + const blob = new Blob([csv], { type: "text/csv" }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.setAttribute("hidden", ""); + a.setAttribute("href", url); + a.setAttribute("download", `${$stockTicker}_price_history.csv`); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } else { + goto("/pricing"); + } + } + $: { if (timePeriod && typeof window !== "undefined") { isLoaded = false; - rawData = prepareDataset(rawData, timePeriod); + rawData = prepareDataset(data?.getData, timePeriod); originalData = rawData; stockList = rawData?.slice(0, 50); + console.log(rawData); isLoaded = true; } } @@ -236,193 +313,303 @@
-
-
-
-
-

- {$stockTicker} Stock Price History -

-
-
- -
-
-
- - - - - - - Select time frame - - - - (timePeriod = "Daily")} - class="cursor-pointer hover:bg-primary" - > - Daily - - (timePeriod = "Weekly")} - class="cursor-pointer hover:bg-primary" - > - Weekly - - (timePeriod = "Monthly")} - class="cursor-pointer hover:bg-primary" - > - Monthly - - (timePeriod = "Quarterly")} - class="cursor-pointer hover:bg-primary" - > - Quarterly - - (timePeriod = "Annual")} - class="cursor-pointer hover:bg-primary" - > - Annual - - - - -
- -
- -
- +
+
+
+
+
-
- - +

+ {$stockTicker} Stock Price History +

+
+
+ + + + + + + Select time frame + + + + (timePeriod = "Daily")} + class="cursor-pointer hover:bg-primary" + > + Daily + + (timePeriod = "Weekly")} + class="cursor-pointer hover:bg-primary" + > + Weekly + + {#if data?.user?.tier !== "Pro"} + {#each ["Monthly", "Quarterly", "Annual"] as entry} + goto("/pricing")} + class="cursor-pointer hover:bg-primary" + > + {entry} + + + {/each} + {:else} + (timePeriod = "Monthly")} + class="cursor-pointer hover:bg-primary" + > + Monthly + + (timePeriod = "Quarterly")} + class="cursor-pointer hover:bg-primary" + > + Quarterly + + (timePeriod = "Annual")} + class="cursor-pointer hover:bg-primary" + > + Annual + + {/if} + + + +
-
- {#each stockList as item, index} - exportData()} + class="ml-2 w-fit border-gray-600 border bg-default sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-2 text-white rounded-md truncate" + > + Download + - + + - {#if rawData?.length !== 0}{:else} - - {/if} + + {#each stockList as item, index} + + + + + + + + + + + {/each} + +
+ + + {#if isLoaded} + {#if rawData?.length !== 0} +
+
+ - {new Date(item?.time).toLocaleString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - daySuffix: "2-digit", - })} - - - - - - - - - - {/each} - -
- {item?.open?.toFixed(2)} - - {item?.high?.toFixed(2)} - - {item?.low?.toFixed(2)} - - {item?.close?.toFixed(2)} - - {item?.change !== null ? item?.change : "n/a"} - - {item?.changesPercentage !== null - ? item?.changesPercentage + "%" - : "n/a"} - - {item?.volume?.toLocaleString("en-US")} -
-
- -
+
+ {#if timePeriod === "Weekly"} + Week of {new Date(item?.time).toLocaleString( + "en-US", + { + month: "short", + day: "numeric", + year: "numeric", + daySuffix: "2-digit", + }, + )} + {:else} + {new Date(item?.time).toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + daySuffix: "2-digit", + })} + {/if} + + {item?.open?.toFixed(2)} + + {item?.high?.toFixed(2)} + + {item?.low?.toFixed(2)} + + {item?.close?.toFixed(2)} + + {item?.change !== null ? item?.change : "n/a"} + + {item?.changesPercentage !== null + ? item?.changesPercentage + "%" + : "n/a"} + + {item?.volume?.toLocaleString("en-US")} +
+
+
+ {:else} + + {/if} + {:else} +
+
+ +
+
+ {/if} +
+ +