Merge pull request #46 from ravixalgorithm/main

feat: implement watchlist sorting by symbol
This commit is contained in:
Mr. Algorithm 2026-02-01 04:25:34 +05:30 committed by GitHub
commit 3e6545ddbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 99 additions and 31 deletions

View File

@ -2,11 +2,10 @@ import React, { Suspense } from 'react';
import { auth } from '@/lib/better-auth/auth'; import { auth } from '@/lib/better-auth/auth';
import { headers } from 'next/headers'; import { headers } from 'next/headers';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { getUserWatchlist, isStockInWatchlist, removeFromWatchlist } from '@/lib/actions/watchlist.actions'; import { getUserWatchlist } from '@/lib/actions/watchlist.actions';
import { getUserAlerts } from '@/lib/actions/alert.actions'; import { getUserAlerts } from '@/lib/actions/alert.actions';
import { getNews } from '@/lib/actions/finnhub.actions'; import { getNews } from '@/lib/actions/finnhub.actions';
import TradingViewWatchlist from '@/components/watchlist/TradingViewWatchlist'; import WatchlistManager from '@/components/watchlist/WatchlistManager';
import WatchlistStockChip from '@/components/watchlist/WatchlistStockChip';
import AlertsPanel from '@/components/watchlist/AlertsPanel'; import AlertsPanel from '@/components/watchlist/AlertsPanel';
import NewsGrid from '@/components/watchlist/NewsGrid'; import NewsGrid from '@/components/watchlist/NewsGrid';
import SearchCommand from '@/components/SearchCommand'; import SearchCommand from '@/components/SearchCommand';
@ -23,16 +22,14 @@ export default async function WatchlistPage() {
const userId = session.user.id; const userId = session.user.id;
// Parallel data fetching
// Parallel data fetching // Parallel data fetching
const [watchlistItems, alerts, news] = await Promise.all([ const [watchlistItems, alerts, news] = await Promise.all([
getUserWatchlist(userId), getUserWatchlist(userId),
getUserAlerts(userId), getUserAlerts(userId),
getNews() // Initial news fetch, maybe refine later to use watchlist symbols getNews() // Initial news fetch
]); ]);
const watchlistSymbols = watchlistItems.map((item: any) => item.symbol); const watchlistSymbols = watchlistItems.map((item: any) => item.symbol);
// const watchlistData = await getWatchlistData(watchlistSymbols); // OPTIMIZATION: Removed to prevent 429 errors. Widget handles data.
// Fallback news if watchlist has items // Fallback news if watchlist has items
const relevantNews = watchlistSymbols.length > 0 ? await getNews(watchlistSymbols) : news; const relevantNews = watchlistSymbols.length > 0 ? await getNews(watchlistSymbols) : news;
@ -56,31 +53,7 @@ export default async function WatchlistPage() {
{/* Main Content - Watchlist Table */} {/* Main Content - Watchlist Table */}
<div className="lg:col-span-3 space-y-8"> <div className="lg:col-span-3 space-y-8">
<div className="space-y-6"> <div className="space-y-6">
{/* Manage Watchlist Section */} <WatchlistManager initialItems={watchlistItems} userId={userId} />
<div className="bg-gray-900/30 rounded-xl border border-gray-800 p-4 backdrop-blur-sm">
<h3 className="text-sm font-semibold text-gray-400 mb-3 uppercase tracking-wider flex items-center">
<span className="mr-2">Manage Symbols</span>
<span className="text-xs bg-gray-800 text-gray-500 px-2 py-0.5 rounded-full">{watchlistSymbols.length}</span>
</h3>
{watchlistSymbols.length > 0 ? (
<div className="flex flex-wrap gap-2">
{watchlistItems.map((item: any) => (
<WatchlistStockChip
key={item.symbol}
symbol={item.symbol}
userId={userId}
/>
))}
</div>
) : (
<p className="text-sm text-gray-500 italic">No stocks in watchlist.</p>
)}
</div>
{/* TradingView Widget */}
<div className="min-h-[550px]">
<TradingViewWatchlist symbols={watchlistSymbols} />
</div>
</div> </div>
{/* News Section */} {/* News Section */}

View File

@ -0,0 +1,95 @@
'use client';
import React, { useState, useMemo } from 'react';
import WatchlistStockChip from './WatchlistStockChip';
import TradingViewWatchlist from './TradingViewWatchlist';
import { Button } from '@/components/ui/button';
import { ArrowDownAZ, ArrowUpZA, ArrowUpDown } from 'lucide-react';
import { WatchlistItem } from '@/database/models/watchlist.model';
interface WatchlistManagerProps {
initialItems: WatchlistItem[]; // Using the DB model type directly or a simplified version
userId: string;
}
export default function WatchlistManager({ initialItems, userId }: WatchlistManagerProps) {
// Sort state: 'asc' (A-Z), 'desc' (Z-A), or null (added order/default)
const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | null>(null);
const toggleSort = () => {
if (sortOrder === null) setSortOrder('asc');
else if (sortOrder === 'asc') setSortOrder('desc');
else setSortOrder(null);
};
const sortedItems = useMemo(() => {
if (!sortOrder) return initialItems;
return [...initialItems].sort((a, b) => {
if (sortOrder === 'asc') {
return a.symbol.localeCompare(b.symbol);
} else {
return b.symbol.localeCompare(a.symbol);
}
});
}, [initialItems, sortOrder]);
const watchlistSymbols = sortedItems.map((item) => item.symbol);
return (
<div className="space-y-6">
<div className="bg-gray-900/30 rounded-xl border border-gray-800 p-4 backdrop-blur-sm">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider flex items-center">
<span className="mr-2">Manage Symbols</span>
<span className="text-xs bg-gray-800 text-gray-500 px-2 py-0.5 rounded-full">
{watchlistSymbols.length}
</span>
</h3>
<Button
variant="ghost"
size="sm"
onClick={toggleSort}
className="h-8 px-2 text-gray-400 hover:text-white hover:bg-white/10"
title={
sortOrder === 'asc'
? 'Sorted A-Z'
: sortOrder === 'desc'
? 'Sorted Z-A'
: 'Default Order'
}
>
{sortOrder === 'asc' && <ArrowDownAZ className="w-4 h-4 mr-2" />}
{sortOrder === 'desc' && <ArrowUpZA className="w-4 h-4 mr-2" />}
{sortOrder === null && <ArrowUpDown className="w-4 h-4 mr-2" />}
<span className="text-xs">
{sortOrder === 'asc'
? 'A-Z'
: sortOrder === 'desc'
? 'Z-A'
: 'Sort'}
</span>
</Button>
</div>
{watchlistSymbols.length > 0 ? (
<div className="flex flex-wrap gap-2">
{sortedItems.map((item) => (
<WatchlistStockChip
key={item.symbol}
symbol={item.symbol}
userId={userId}
/>
))}
</div>
) : (
<p className="text-sm text-gray-500 italic">No stocks in watchlist.</p>
)}
</div>
<div className="min-h-[550px]">
<TradingViewWatchlist symbols={watchlistSymbols} />
</div>
</div>
);
}