extend bulk download

This commit is contained in:
MuslemRahimi 2025-04-13 13:26:40 +02:00
parent f92bcb6603
commit 5d5ac25457
2 changed files with 106 additions and 46 deletions

View File

@ -1,16 +1,24 @@
import type { RequestHandler } from "./$types"; import type { RequestHandler } from "./$types";
export const POST: RequestHandler = async ({ request, locals }) => { export const POST: RequestHandler = async ({ request, locals }) => {
const { apiURL, apiKey, user, pb } = locals;
const data = await request.json(); const data = await request.json();
const tickers = data?.tickers; // tickers assumed to be an array const tickers = data?.tickers; // tickers assumed to be an array
const { apiURL, apiKey, user, pb } = locals; let bulkData = data?.bulkData;
const totalCreditCost =
tickers?.length *
bulkData?.reduce((sum, item) => {
return item.selected ? sum + item.credit : sum;
}, 0);
// Check if user has enough credits // Check if user has enough credits
if (user?.credits < tickers?.length) { if (user?.credits < totalCreditCost) {
return new Response(JSON.stringify({ error: "Insufficient credits" }), { status: 400 }); return new Response(JSON.stringify({ error: "Insufficient credits" }), { status: 400 });
} }
const postData = { tickers }; const postData = { tickers, bulkData };
const response = await fetch(apiURL + "/bulk-download", { const response = await fetch(apiURL + "/bulk-download", {
method: "POST", method: "POST",
headers: { headers: {
@ -22,7 +30,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
// Deduct credits after a successful request // Deduct credits after a successful request
await pb.collection('users').update(user?.id, { await pb.collection('users').update(user?.id, {
'credits': user?.credits - tickers?.length 'credits': user?.credits - totalCreditCost
}); });
const contentType = response.headers.get("content-type") || ""; const contentType = response.headers.get("content-type") || "";

View File

@ -29,9 +29,9 @@
let searchQuery = ""; let searchQuery = "";
let switchWatchlist = false; let switchWatchlist = false;
let editMode = false; let editMode = false;
let realtimeUpdates = true;
let numberOfChecked = 0; let numberOfChecked = 0;
let activeIdx = 0; let activeIdx = 0;
let totalCreditCost = 0;
let rawTabData = []; let rawTabData = [];
let deleteTickerList = []; let deleteTickerList = [];
@ -47,6 +47,23 @@
let checkedItems; let checkedItems;
let socket; let socket;
let bulkData = [
{
name: "Price Data",
selected: true,
credit: 1,
},
{
name: "Dividends Data",
selected: true,
credit: 1,
},
{
name: "Options Data",
selected: true,
credit: 3,
},
];
const tabs = [ const tabs = [
{ {
title: "News", title: "News",
@ -1048,14 +1065,25 @@
watchList = [...originalData].sort(compareValues)?.slice(0, 50); watchList = [...originalData].sort(compareValues)?.slice(0, 50);
}; };
async function downloadHistoricalData() { async function handleBulkDownload() {
const tickers = watchList?.map((item) => item?.symbol); // example tickers const tickers = watchList?.map((item) => item?.symbol); // example tickers
if (data?.user?.credits > tickers?.length && tickers?.length > 0) {
data.user.credits = data?.user?.credits - tickers?.length; if (totalCreditCost === 0 || tickers?.length === 0) {
toast.error(
`Select at least one ${tickers?.length === 0 ? "symbol" : "bulk data"} to download`,
{
style: `border-radius: 5px; background: #fff; color: #000; border-color: ${$mode === "light" ? "#F9FAFB" : "#4B5563"}; font-size: 15px;`,
},
);
return;
}
if (data?.user?.credits > totalCreditCost && tickers?.length > 0) {
data.user.credits = data?.user?.credits - totalCreditCost;
const response = await fetch("/api/bulk-download", { const response = await fetch("/api/bulk-download", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tickers }), body: JSON.stringify({ tickers: tickers, bulkData: bulkData }),
}); });
if (response.ok) { if (response.ok) {
@ -1079,6 +1107,18 @@
}); });
} }
} }
$: {
if (bulkData) {
const tickers = watchList?.map((item) => item?.symbol); // example tickers
totalCreditCost =
tickers?.length *
bulkData?.reduce((sum, item) => {
return item.selected ? sum + item.credit : sum;
}, 0);
}
}
</script> </script>
<SEO <SEO
@ -1527,7 +1567,7 @@
class="shadow-sm min-w-[110px] w-full sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-200 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-2.5 rounded truncate" class="shadow-sm min-w-[110px] w-full sm:w-fit border-gray-300 dark:border-gray-600 border sm:hover:bg-gray-200 dark:sm:hover:bg-primary ease-out flex flex-row justify-between items-center px-3 py-2.5 rounded truncate"
> >
<span class="truncate text-sm sm:text-[1rem]" <span class="truncate text-sm sm:text-[1rem]"
>Options</span >Bulk Download</span
> >
<svg <svg
class="-mr-1 ml-2 h-5 w-5 inline-block" class="-mr-1 ml-2 h-5 w-5 inline-block"
@ -1546,51 +1586,63 @@
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content <DropdownMenu.Content
class="w-auto max-w-60 max-h-[400px] overflow-y-auto scroller relative" class="w-auto max-w-80 max-h-[400px] overflow-y-auto scroller relative"
> >
<DropdownMenu.Label
class="text-muted dark:text-gray-400 font-semibold dark:font-normal text-xs"
>
{data?.user?.credits} Credits left
</DropdownMenu.Label>
<!-- Dropdown items --> <!-- Dropdown items -->
<DropdownMenu.Group class="pb-2"> <DropdownMenu.Group class="pb-2">
<!-- Added padding to avoid overlapping with Reset button --> {#each bulkData as item}
<DropdownMenu.Item <DropdownMenu.Item
class="sm:hover:bg-gray-200 dark:sm:hover:bg-primary cursor-pointer mt-2" class="sm:hover:bg-gray-200 dark:sm:hover:bg-primary"
>
<button
on:click={downloadHistoricalData}
class="flex items-center cursor-pointer"
> >
Bulk Download <span class="ml-2 text-xs" <label
>({data?.user?.credits} Credits left)</span on:click|capture={(event) => {
event.preventDefault();
item.selected = !item?.selected;
}}
class="inline-flex justify-between w-full items-center cursor-pointer"
> >
</button> <span class="mr-2 text-sm">{item?.name}</span>
</DropdownMenu.Item> <span class="mr-2 text-xs inline-block"
<DropdownMenu.Item >({item?.credit} Credits)</span
class="sm:hover:bg-gray-200 dark:sm:hover:bg-primary" >
> <div class="relative ml-auto">
<label <input
on:click|capture={(event) => { type="checkbox"
event.preventDefault(); checked={item?.selected}
realtimeUpdates = !realtimeUpdates; class="sr-only peer"
}} />
class="inline-flex justify-between w-full items-center cursor-pointer" <div
> class="w-9 h-5 bg-gray-400 rounded-full peer peer-checked:bg-blue-600
<span class="mr-2 text-sm">Realtime Updates</span>
<div class="relative ml-auto">
<input
type="checkbox"
bind:checked={realtimeUpdates}
class="sr-only peer"
/>
<div
class="w-9 h-5 bg-gray-400 rounded-full peer peer-checked:bg-blue-600
after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:content-[''] after:absolute after:top-0.5 after:left-[2px]
after:bg-white after:border-gray-300 after:border after:bg-white after:border-gray-300 after:border
after:rounded-full after:h-4 after:w-4 after:transition-all after:rounded-full after:h-4 after:w-4 after:transition-all
peer-checked:after:translate-x-full" peer-checked:after:translate-x-full"
></div> ></div>
</div></label </div></label
> >
</DropdownMenu.Item> </DropdownMenu.Item>
{/each}
</DropdownMenu.Group> </DropdownMenu.Group>
<div
class="sticky -bottom-1 bg-white dark:bg-default z-50 p-2 border-t border-gray-300 dark:border-gray-600 w-full flex justify-between items-center"
>
<span
class="w-full text-muted dark:text-gray-300 bg-white dark:bg-default font-semibold dark:font-normal text-start text-xs select-none"
>
= Credit Cost {totalCreditCost}
</span>
<button
on:click={handleBulkDownload}
class="w-full flex justify-end dark:sm:hover:text-white text-muted dark:text-gray-300 bg-white dark:bg-default text-start text-sm cursor-pointer"
>
Bulk Download
</button>
</div>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
</div> </div>