Files
stock-scanner/src/components/Scanner.js
2026-08-07 22:38:27 +05:30

211 lines
8.8 KiB
JavaScript

import React, { useEffect, useState } from "react";
import axios from "axios";
import AboutScanner from "./AboutScanner";
function Scanner({ onSelectStock, onSignal, onBuyClick }) {
const [data, setData] = useState(null);
const [lastBest, setLastBest] = useState(null);
const loadData = async () => {
try {
const res = await axios.get("/api/scanner/scan");
const newData = res.data;
const best = newData.best_5 && newData.best_5.length > 0 ? newData.best_5[0] : null;
if (best && best.symbol !== lastBest) {
if (onSignal) onSignal(`New Best Trade: ${best.symbol}`);
setLastBest(best.symbol);
}
setData(newData);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
loadData();
const interval = setInterval(loadData, 100000);
return () => clearInterval(interval);
}, []);
const openChart = (symbol) => {
window.open(`https://www.tradingview.com/chart/?symbol=NSE:${symbol}`, "_blank");
};
const removeStockFromSector = async (symbol, sector) => {
try {
await axios.delete("/api/scanner/sectors", {
data: { sector, symbol }
});
setData(prev => {
const newData = JSON.parse(JSON.stringify(prev));
if (newData.sectors && newData.sectors[sector]) {
newData.sectors[sector].stocks = newData.sectors[sector].stocks.filter(s => s.symbol !== symbol);
}
if (newData.best_5) {
newData.best_5 = newData.best_5.filter(s => s.symbol !== symbol);
}
return newData;
});
} catch (err) {
console.error("Error removing stock", err);
}
};
if (!data) return (
<>
<AboutScanner isLoaded={false} />
<div className="glass-card" style={{ textAlign: "center" }}>Loading Scanner Data (this may take up to 60 seconds for large watchlists)...</div>
</>
);
return (
<div>
<AboutScanner isLoaded={true} />
{/* Top 5 Trades */}
{(data.best_5 || []).length > 0 && (
<div className="glass-card animate-fade-in" style={{ marginBottom: "30px", borderLeft: "4px solid var(--accent-green)" }}>
<h2 className="text-green">🔥 Top 10 Momentum Picks</h2>
<div className="data-table-container">
<table className="data-table">
<thead>
<tr>
<th>Stock</th>
<th>Price</th>
<th>RSI</th>
<th>MACD</th>
<th>Signal</th>
<th>AI Score</th>
<th>Grade</th>
<th>Institutional</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{(data.best_5 || []).filter(s => s).map((s, i) => (
<tr key={i}>
<td style={{ fontWeight: 600 }}>{s?.symbol}</td>
<td>{s?.price}</td>
<td><span className={`badge ${s?.rsi > 60 ? 'badge-success' : 'badge-neutral'}`}>{s?.rsi}</span></td>
<td><span className={`badge ${s?.macd_hist > 0 ? 'badge-success' : 'badge-danger'}`}>{s?.macd_hist}</span></td>
<td>
<span className={`badge ${s?.signal?.includes("BUY") ? 'badge-success' : 'badge-danger'}`}>
{s?.signal}
</span>
</td>
<td>{s.ai_score} / 100</td>
<td>{s.grade}</td>
<td>{s.institutional ? <span className="text-orange">🔥 Yes</span> : <span className="text-muted">No</span>}</td>
<td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<button className="action-btn" onClick={() => { openChart(s?.symbol); onSelectStock(s?.symbol); }}>
View Chart
</button>
<button
onClick={() => onBuyClick(s?.symbol, s?.price || 0)}
style={{ background: 'var(--accent-green)', color: '#1a1a2e', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Buy
</button>
{s?.sector && (
<button
onClick={() => removeStockFromSector(s?.symbol, s.sector)}
title={`Remove from ${s.sector}`}
style={{ background: 'none', border: 'none', color: 'var(--accent-red)', cursor: 'pointer', padding: '0', display: 'flex', alignItems: 'center' }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h18"></path>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Sector Tables */}
{Object.entries(data.sectors || {}).map(([sector, info]) => {
const best = info.top_stock;
return (
<div key={sector} className="glass-card animate-fade-in" style={{ marginBottom: "20px" }}>
<h2 className="text-blue">{sector} ({info.stocks.length} Stocks)</h2>
{best && (
<p className="text-orange" style={{ marginBottom: "20px" }}>
<strong>Top Pick:</strong> {best.symbol} (Score: {best.score})
</p>
)}
<div className="data-table-container">
<table className="data-table">
<thead>
<tr>
<th>Stock</th>
<th>Price</th>
<th>MACD</th>
<th>Signal</th>
<th>Volume</th>
<th>Target</th>
<th>AI Score</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{(info.stocks || []).map((s, i) => (
<tr key={i}>
<td style={{ fontWeight: 500 }}>{s?.symbol}</td>
<td>{s?.price}</td>
<td><span className={`badge ${s?.macd_hist > 0 ? 'badge-success' : 'badge-danger'}`}>{s?.macd_hist}</span></td>
<td>
<span className={`badge ${s?.signal?.includes("BUY") ? 'badge-success' : 'badge-danger'}`}>
{s?.signal}
</span>
</td>
<td>{s?.volume?.toLocaleString()}</td>
<td className="text-green">{s?.target}</td>
<td>{s?.ai_score}</td>
<td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<button
className="action-btn"
style={{ background: 'var(--accent-green)', color: '#1a1a2e', padding: '4px 8px', fontSize: '0.8rem' }}
onClick={() => onBuyClick(s?.symbol, s?.price)}
>
Buy
</button>
<button className="action-btn" onClick={() => { openChart(s?.symbol); onSelectStock(s?.symbol); }}>
Chart
</button>
<button
onClick={() => removeStockFromSector(s?.symbol, sector)}
title={`Remove from ${sector}`}
style={{ background: 'none', border: 'none', color: 'var(--accent-red)', cursor: 'pointer', padding: '0', display: 'flex', alignItems: 'center' }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h18"></path>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
})}
</div>
);
}
export default Scanner;