feat: implement watchlist sorting by symbol
This commit is contained in:
parent
a4d18dc06d
commit
d6d6323077
|
|
@ -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 */}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue