commit - mock buy

This commit is contained in:
2026-08-07 22:38:27 +05:30
parent 53ad1abb78
commit e4ad2233b8
19 changed files with 511 additions and 47 deletions

Binary file not shown.

View File

@@ -0,0 +1,102 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from pydantic import BaseModel
import yfinance as yf
from app.db.database import get_db
from app.db.models import User, PortfolioItem
from app.api.auth import get_current_user
router = APIRouter()
class BuyRequest(BaseModel):
symbol: str
quantity: int
buy_price: float
class SellRequest(BaseModel):
portfolio_item_id: int
@router.post("/buy")
def buy_stock(req: BuyRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
final_buy_price = req.buy_price
# If price wasn't provided (e.g. from watchlist), fetch it live
if final_buy_price <= 0:
try:
ticker = yf.Ticker(req.symbol)
final_buy_price = ticker.fast_info.last_price
except Exception as e:
print(f"Error fetching live price on buy for {req.symbol}: {e}")
raise HTTPException(status_code=400, detail="Could not fetch live price, please try again.")
new_item = PortfolioItem(
user_id=current_user.id,
symbol=req.symbol,
quantity=req.quantity,
buy_price=final_buy_price
)
db.add(new_item)
db.commit()
db.refresh(new_item)
return {"message": "Stock purchased successfully", "item": new_item}
@router.get("/")
def get_portfolio(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
items = db.query(PortfolioItem).filter(PortfolioItem.user_id == current_user.id).all()
portfolio = []
total_invested = 0
total_current_value = 0
for item in items:
# Fetch live price
live_price = item.buy_price # Fallback
try:
ticker = yf.Ticker(item.symbol)
live_price = ticker.fast_info.last_price
except Exception as e:
print(f"Error fetching price for {item.symbol}: {e}")
invested = item.quantity * item.buy_price
current_value = item.quantity * live_price
pnl = current_value - invested
pnl_percent = (pnl / invested) * 100 if invested > 0 else 0
total_invested += invested
total_current_value += current_value
portfolio.append({
"id": item.id,
"symbol": item.symbol,
"quantity": item.quantity,
"buy_price": item.buy_price,
"current_price": round(live_price, 2),
"invested": round(invested, 2),
"current_value": round(current_value, 2),
"pnl": round(pnl, 2),
"pnl_percent": round(pnl_percent, 2),
"purchase_date": item.purchase_date
})
total_pnl = total_current_value - total_invested
total_pnl_percent = (total_pnl / total_invested) * 100 if total_invested > 0 else 0
return {
"items": portfolio,
"summary": {
"total_invested": round(total_invested, 2),
"total_current_value": round(total_current_value, 2),
"total_pnl": round(total_pnl, 2),
"total_pnl_percent": round(total_pnl_percent, 2)
}
}
@router.delete("/{item_id}")
def delete_portfolio_item(item_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
item = db.query(PortfolioItem).filter(PortfolioItem.id == item_id, PortfolioItem.user_id == current_user.id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
db.delete(item)
db.commit()
return {"message": "Stock sold/removed successfully"}

View File

@@ -1,6 +1,6 @@
import os
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:M%40triXPostgr3s%406202@host.docker.internal:5333/stock_scanner")
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:M%40triXPostgr3s%406202@192.168.0.111:5432/stock_scanner")
SECRET_KEY = "stock-scanner-super-secret-key-12345"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60

View File

@@ -1,5 +1,6 @@
from sqlalchemy import Column, Integer, String, JSON
from sqlalchemy import Column, Integer, String, JSON, Float, ForeignKey, DateTime
from app.db.database import Base
import datetime
class User(Base):
__tablename__ = "users"
@@ -12,3 +13,13 @@ class User(Base):
mobile_no = Column(String, nullable=True)
gender = Column(String, nullable=True)
sector_map = Column(JSON, nullable=True)
class PortfolioItem(Base):
__tablename__ = "portfolio_items"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"))
symbol = Column(String, index=True)
quantity = Column(Integer, default=1)
buy_price = Column(Float)
purchase_date = Column(DateTime, default=datetime.datetime.utcnow)

View File

@@ -1,6 +1,6 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api import auth, scanner
from app.api import auth, scanner, portfolio
from app.db.database import engine, Base
# Initialize the database tables
@@ -20,6 +20,7 @@ app.add_middleware(
# Include Routers
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(scanner.router, prefix="/api/scanner", tags=["scanner"])
app.include_router(portfolio.router, prefix="/api/portfolio", tags=["portfolio"])
@app.get("/")
def read_root():

50
build_and_push.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/bin/bash
# Configuration
REGISTRY="hub.technobeesolutions.in"
USERNAME="technobee_admin"
PASSWORD='M@tr!x#149@dm!N'
BACKEND_IMAGE="${REGISTRY}/stock-scanner-backend:latest"
FRONTEND_IMAGE="${REGISTRY}/stock-scanner-frontend:latest"
PLATFORM="linux/amd64"
echo "=========================================="
echo " Stock Scanner - Docker Build & Push "
echo "=========================================="
# 1. Login to Docker Registry
echo "[1/4] Logging into Docker Registry ($REGISTRY)..."
# Using --password-stdin is the secure way to pass passwords to docker login
echo "$PASSWORD" | docker login "$REGISTRY" -u "$USERNAME" --password-stdin
if [ $? -ne 0 ]; then
echo "❌ Docker login failed! Please check your credentials and ensure Docker is running."
exit 1
fi
echo "✅ Login successful."
echo ""
# 2. Build and push backend
echo "[2/4] Building and pushing Backend Image for $PLATFORM..."
docker buildx build --platform "$PLATFORM" -t "$BACKEND_IMAGE" -f backend/Dockerfile.backend ./backend --push
if [ $? -ne 0 ]; then
echo "❌ Backend build/push failed!"
exit 1
fi
echo "✅ Backend image built and pushed successfully."
echo ""
# 3. Build and push frontend
echo "[3/4] Building and pushing Frontend Image for $PLATFORM..."
docker buildx build --platform "$PLATFORM" -t "$FRONTEND_IMAGE" -f Dockerfile.frontend . --push
if [ $? -ne 0 ]; then
echo "❌ Frontend build/push failed!"
exit 1
fi
echo "✅ Frontend image built and pushed successfully."
echo ""
echo "=========================================="
echo "🎉 Build and Push Complete!"
echo "You can now run 'docker compose pull && docker compose up -d' on your target server."
echo "=========================================="

View File

@@ -16,4 +16,4 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- DATABASE_URL=postgresql://postgres:M%40triXPostgr3s%406202@host.docker.internal:5333/stock_scanner
- DATABASE_URL=postgresql://postgres:M%40triXPostgr3s%406202@host.docker.internal:5432/stock_scanner

18
run_backend.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
echo "Starting Stock Scanner Backend..."
# Navigate to the backend directory
cd backend || exit 1
# Activate virtual environment if it exists (optional, helps if user uses venv)
if [ -d "venv" ]; then
echo "Activating virtual environment..."
source venv/bin/activate
elif [ -d "../venv" ]; then
echo "Activating virtual environment..."
source ../venv/bin/activate
fi
# Run the uvicorn server with hot reload
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

View File

@@ -24,6 +24,8 @@ function Dashboard() {
const [userProfile, setUserProfile] = useState(null);
const [dropdownOpen, setDropdownOpen] = useState(false);
const [scannerLoaded, setScannerLoaded] = useState(false);
const [buyModalData, setBuyModalData] = useState(null);
const [buyQuantity, setBuyQuantity] = useState(1);
const navigate = useNavigate();
const fetchProfile = () => {
@@ -43,34 +45,55 @@ function Dashboard() {
navigate("/login");
};
const handleBuySubmit = async () => {
if (!buyModalData || buyQuantity < 1) return;
try {
await axios.post("/api/portfolio/buy", {
symbol: buyModalData.symbol,
quantity: buyQuantity,
buy_price: buyModalData.price
});
setBuyModalData(null);
setBuyQuantity(1);
} catch (err) {
console.error("Error buying stock:", err);
alert("Failed to buy stock");
}
};
const onBuyClick = (symbol, price) => {
setBuyModalData({ symbol, price });
setBuyQuantity(1);
};
return (
<div className="app-container">
{/* HEADER */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', position: 'relative' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
{/* Logo only, no text */}
<span style={{ fontSize: '2.5rem' }}>📊</span>
<h1 style={{ margin: 0 }}>Stock Scanner Pro</h1>
</div>
{userProfile && (
<div style={{ position: 'relative' }}>
<div
style={{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer', background: 'var(--glass-bg)', padding: '8px 16px', borderRadius: '30px', border: '1px solid var(--border-color)', backdropFilter: 'blur(10px)' }}
style={{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer', background: 'var(--glass-bg)', padding: '6px 12px', borderRadius: '30px', border: '1px solid var(--border-color)', backdropFilter: 'blur(10px)' }}
onClick={() => setDropdownOpen(!dropdownOpen)}
>
<div style={{ width: '35px', height: '35px', borderRadius: '50%', background: 'linear-gradient(135deg, var(--accent-blue), var(--accent-green))', display: 'flex', justifyContent: 'center', alignItems: 'center', fontWeight: 'bold', fontSize: '1.2rem', color: 'white' }}>
<div style={{ width: '30px', height: '30px', borderRadius: '50%', background: 'linear-gradient(135deg, var(--accent-blue), var(--accent-green))', display: 'flex', justifyContent: 'center', alignItems: 'center', fontWeight: 'bold', fontSize: '1rem', color: 'white' }}>
{userProfile.display_name ? userProfile.display_name.charAt(0).toUpperCase() : '?'}
</div>
<span style={{ fontWeight: 600 }}>{userProfile.display_name || userProfile.username}</span>
<span style={{ fontWeight: 600, fontSize: '0.9rem' }}>{userProfile.display_name || userProfile.username}</span>
<span style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}></span>
</div>
{dropdownOpen && (
<div className="glass-card animate-fade-in" style={{ position: 'absolute', top: '55px', right: '0', width: '220px', zIndex: 100, padding: '10px' }}>
<button className="nav-btn" style={{ width: '100%', marginBottom: '8px', border: 'none', whiteSpace: 'nowrap' }} onClick={() => { setView('profile'); setDropdownOpen(false); }}>
<div className="glass-card animate-fade-in" style={{ position: 'absolute', top: '50px', right: '0', width: '200px', zIndex: 100, padding: '8px' }}>
<button className="nav-btn" style={{ width: '100%', marginBottom: '8px', border: 'none', whiteSpace: 'nowrap', padding: '8px' }} onClick={() => { setView('profile'); setDropdownOpen(false); }}>
Profile Settings
</button>
<button className="nav-btn" style={{ width: '100%', border: 'none', background: 'var(--accent-red-bg)', color: 'var(--accent-red)', whiteSpace: 'nowrap' }} onClick={handleLogout}>
<button className="nav-btn" style={{ width: '100%', border: 'none', background: 'var(--accent-red-bg)', color: 'var(--accent-red)', whiteSpace: 'nowrap', padding: '8px' }} onClick={handleLogout}>
🚪 Logout
</button>
</div>
@@ -79,14 +102,14 @@ function Dashboard() {
)}
</div>
<div className="nav-container">
<button className={`nav-btn ${view === "dashboard" ? "active" : ""}`} onClick={() => setView("dashboard")} title="Dashboard" style={{ padding: '8px 12px' }}>🏠</button>
<button className={`nav-btn ${view === "scanner" ? "active" : ""}`} onClick={() => setView("scanner")}>📊 Scanner</button>
<button className={`nav-btn ${view === "ribbon" ? "active" : ""}`} onClick={() => setView("ribbon")}>📈 Ribbon Strategy</button>
<button className={`nav-btn ${view === "smartmoney" ? "active" : ""}`} onClick={() => setView("smartmoney")}>🏦 Smart Money</button>
<button className={`nav-btn ${view === "trend" ? "active" : ""}`} onClick={() => setView("trend")}> Trend Analysis</button>
<button className={`nav-btn ${view === "marketintel" ? "active" : ""}`} onClick={() => setView("marketintel")}>📰 Market Intel</button>
<button className={`nav-btn ${view === "watchlists" ? "active" : ""}`} onClick={() => setView("watchlists")}>📋 Watchlists</button>
<div className="nav-container" style={{ gap: '8px' }}>
<button className={`nav-btn ${view === "dashboard" ? "active" : ""}`} onClick={() => setView("dashboard")} title="Dashboard" style={{ padding: '6px 12px' }}>🏠</button>
<button className={`nav-btn ${view === "scanner" ? "active" : ""}`} onClick={() => setView("scanner")} style={{ padding: '6px 12px', fontSize: '0.9rem' }}>📊 Scanner</button>
<button className={`nav-btn ${view === "ribbon" ? "active" : ""}`} onClick={() => setView("ribbon")} style={{ padding: '6px 12px', fontSize: '0.9rem' }}>📈 Ribbon</button>
<button className={`nav-btn ${view === "smartmoney" ? "active" : ""}`} onClick={() => setView("smartmoney")} style={{ padding: '6px 12px', fontSize: '0.9rem' }}>🏦 Smart Money</button>
<button className={`nav-btn ${view === "trend" ? "active" : ""}`} onClick={() => setView("trend")} style={{ padding: '6px 12px', fontSize: '0.9rem' }}> Trend</button>
<button className={`nav-btn ${view === "marketintel" ? "active" : ""}`} onClick={() => setView("marketintel")} style={{ padding: '6px 12px', fontSize: '0.9rem' }}>📰 Intel</button>
<button className={`nav-btn ${view === "watchlists" ? "active" : ""}`} onClick={() => setView("watchlists")} style={{ padding: '6px 12px', fontSize: '0.9rem' }}>📋 Watchlists</button>
</div>
<div className="animate-fade-in">
@@ -95,6 +118,7 @@ function Dashboard() {
<Scanner
onSelectStock={setSelectedStock}
onSignal={setSignal}
onBuyClick={onBuyClick}
/>
<div className="glass-card" style={{ marginTop: "20px" }}>
<h2>TradingView Chart</h2>
@@ -103,14 +127,60 @@ function Dashboard() {
{signal && <Popup signal={signal} />}
</>
)}
{view === "dashboard" && <MainDashboard userProfile={userProfile} />}
{view === "ribbon" && <Ribbon />}
{view === "smartmoney" && <SmartMoney />}
{view === "marketintel" && <MarketIntel />}
{view === "trend" && <TrendScanner />}
{view === "watchlists" && <WatchlistManager userProfile={userProfile} onProfileUpdated={fetchProfile} />}
{view === "dashboard" && <MainDashboard userProfile={userProfile} onBuyClick={onBuyClick} />}
{view === "ribbon" && <Ribbon onBuyClick={onBuyClick} />}
{view === "smartmoney" && <SmartMoney onBuyClick={onBuyClick} />}
{view === "marketintel" && <MarketIntel onBuyClick={onBuyClick} />}
{view === "trend" && <TrendScanner onBuyClick={onBuyClick} />}
{view === "watchlists" && <WatchlistManager userProfile={userProfile} onProfileUpdated={fetchProfile} onBuyClick={onBuyClick} />}
{view === "profile" && <Profile userProfile={userProfile} onProfileUpdated={fetchProfile} />}
</div>
{/* Buy Modal */}
{buyModalData && (
<div style={{
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
backgroundColor: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(5px)',
display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999
}}>
<div style={{
background: 'var(--glass-bg)', padding: '30px', borderRadius: '15px',
border: '1px solid var(--border-color)', width: 'min(350px, 90vw)', textAlign: 'center',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)'
}}>
<h2 style={{ marginBottom: '10px' }}>Mock Buy</h2>
<h3 style={{ color: 'var(--accent-green)', margin: '10px 0', fontSize: '1.8rem' }}>{buyModalData.symbol}</h3>
<p style={{ color: 'var(--text-secondary)' }}>Current Price: <strong style={{ color: 'var(--text-color)' }}>{buyModalData.price}</strong></p>
<div style={{ margin: '25px 0', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '15px' }}>
<label style={{ fontWeight: '500' }}>Quantity: </label>
<input
type="number"
value={buyQuantity}
onChange={(e) => setBuyQuantity(parseInt(e.target.value) || 1)}
min="1"
style={{
background: 'var(--search-bg)', border: '1px solid var(--border-color)',
color: 'var(--text-color)', padding: '10px', borderRadius: '8px', width: '100px',
fontSize: '1.1rem', textAlign: 'center'
}}
/>
</div>
<div style={{ margin: '20px 0', padding: '15px', background: 'rgba(0,0,0,0.2)', borderRadius: '10px' }}>
<span style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>Total Investment</span>
<p style={{ fontWeight: 'bold', fontSize: '1.4rem', margin: '5px 0 0 0', color: 'var(--accent-blue)' }}>
{(buyModalData.price * buyQuantity).toFixed(2)}
</p>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '15px', marginTop: '25px' }}>
<button className="nav-btn" style={{ flex: 1, padding: '12px' }} onClick={() => setBuyModalData(null)}>Cancel</button>
<button className="action-btn" onClick={handleBuySubmit} style={{ flex: 1, padding: '12px', background: 'linear-gradient(135deg, var(--accent-blue), var(--accent-green))', color: '#1a1a2e', border: 'none', fontWeight: 'bold' }}>Confirm Buy</button>
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -1,12 +1,40 @@
import React, { useState, useRef, useEffect } from "react";
import axios from "axios";
function MainDashboard({ userProfile }) {
function MainDashboard({ userProfile, onBuyClick }) {
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [searching, setSearching] = useState(false);
const searchTimeout = useRef(null);
const [portfolio, setPortfolio] = useState([]);
const [portfolioSummary, setPortfolioSummary] = useState(null);
const [loadingPortfolio, setLoadingPortfolio] = useState(true);
useEffect(() => {
fetchPortfolio();
}, []);
const fetchPortfolio = async () => {
try {
const res = await axios.get("/api/portfolio/");
setPortfolio(res.data.items);
setPortfolioSummary(res.data.summary);
} catch (err) {
console.error("Error fetching portfolio:", err);
}
setLoadingPortfolio(false);
};
const sellStock = async (id) => {
try {
await axios.delete(`/api/portfolio/${id}`);
fetchPortfolio(); // refresh
} catch (err) {
console.error("Error selling stock:", err);
}
};
const formatMarketCap = (cap) => {
if (!cap) return "N/A";
if (cap >= 1e12) return "₹" + (cap / 1e12).toFixed(2) + "T";
@@ -134,6 +162,13 @@ function MainDashboard({ userProfile }) {
)}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
<button
onClick={(e) => { e.stopPropagation(); onBuyClick(result.symbol, result.currentPrice); }}
style={{ background: 'linear-gradient(135deg, var(--accent-blue), var(--accent-green))', color: '#1a1a2e', padding: '6px 12px', borderRadius: '8px', border: 'none', fontWeight: 'bold', cursor: 'pointer' }}
>
Buy
</button>
<div style={{ color: 'var(--accent-green)', display: 'flex', alignItems: 'center', gap: '5px' }}>
<span>Chart</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -143,11 +178,95 @@ function MainDashboard({ userProfile }) {
</svg>
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* Portfolio Section */}
<div style={{ width: '100%', marginTop: '50px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}><span style={{ fontSize: '1.8rem' }}>💼</span> Mock Portfolio</h2>
<span style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>As of: {new Date().toLocaleDateString()}</span>
</div>
{loadingPortfolio ? (
<p>Loading portfolio...</p>
) : portfolio.length === 0 ? (
<div className="glass-card" style={{ textAlign: 'center', padding: '40px' }}>
<p style={{ color: 'var(--text-muted)' }}>Your mock portfolio is empty.</p>
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Search for a stock or run a scanner to simulate buying.</p>
</div>
) : (
<>
{/* Summary Cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '20px', marginBottom: '30px' }}>
<div className="glass-card" style={{ textAlign: 'center' }}>
<p style={{ color: 'var(--text-secondary)', margin: '0 0 10px 0' }}>Total Invested</p>
<h3 style={{ margin: 0 }}>{portfolioSummary.total_invested.toLocaleString()}</h3>
</div>
<div className="glass-card" style={{ textAlign: 'center' }}>
<p style={{ color: 'var(--text-secondary)', margin: '0 0 10px 0' }}>Current Value</p>
<h3 style={{ margin: 0, color: 'var(--accent-blue)' }}>{portfolioSummary.total_current_value.toLocaleString()}</h3>
</div>
<div className="glass-card" style={{ textAlign: 'center', border: `1px solid ${portfolioSummary.total_pnl >= 0 ? 'var(--accent-green)' : 'var(--accent-red)'}` }}>
<p style={{ color: 'var(--text-secondary)', margin: '0 0 10px 0' }}>Overall P&L</p>
<h3 style={{ margin: 0, color: portfolioSummary.total_pnl >= 0 ? 'var(--accent-green)' : 'var(--accent-red)' }}>
{portfolioSummary.total_pnl >= 0 ? '+' : ''}{portfolioSummary.total_pnl.toLocaleString()}
<span style={{ fontSize: '1rem', marginLeft: '8px' }}>({portfolioSummary.total_pnl_percent.toFixed(2)}%)</span>
</h3>
</div>
</div>
{/* Holdings Table */}
<div className="glass-card" style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr>
<th>Symbol</th>
<th>Qty</th>
<th>DOP</th>
<th>Buy Price</th>
<th>Invested</th>
<th>LTP</th>
<th>Current Value</th>
<th>P&L</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{portfolio.map(item => (
<tr key={item.id}>
<td style={{ fontWeight: 'bold', color: 'var(--accent-blue)', cursor: 'pointer' }} onClick={() => openChart(item.symbol)}>
{item.symbol}
</td>
<td>{item.quantity}</td>
<td style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
<div>{new Date(item.purchase_date).toLocaleDateString()}</div>
<div style={{ fontSize: '0.75rem', color: 'var(--accent-blue)', marginTop: '2px' }}>
{Math.max(0, Math.floor((new Date() - new Date(item.purchase_date)) / (1000 * 60 * 60 * 24)))} days
</div>
</td>
<td>{item.buy_price.toFixed(2)}</td>
<td>{item.invested.toLocaleString()}</td>
<td>{item.current_price.toFixed(2)}</td>
<td>{item.current_value.toLocaleString()}</td>
<td style={{ color: item.pnl >= 0 ? 'var(--accent-green)' : 'var(--accent-red)', fontWeight: 'bold' }}>
{item.pnl >= 0 ? '+' : ''}{item.pnl.toLocaleString()} ({item.pnl_percent.toFixed(2)}%)
</td>
<td>
<button className="delete-btn" onClick={() => sellStock(item.id)} title="Sell / Remove">Sell</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)}
</div>
</div>
);
}

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
function Ribbon() {
function Ribbon({ onBuyClick }) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
@@ -77,6 +77,13 @@ function Ribbon() {
</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)}>
Chart
</button>

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import axios from "axios";
import AboutScanner from "./AboutScanner";
function Scanner({ onSelectStock, onSignal }) {
function Scanner({ onSelectStock, onSignal, onBuyClick }) {
const [data, setData] = useState(null);
const [lastBest, setLastBest] = useState(null);
@@ -68,7 +68,7 @@ function Scanner({ onSelectStock, onSignal }) {
{/* 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 5 Momentum Picks</h2>
<h2 className="text-green">🔥 Top 10 Momentum Picks</h2>
<div className="data-table-container">
<table className="data-table">
<thead>
@@ -104,6 +104,12 @@ function Scanner({ onSelectStock, onSignal }) {
<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)}
@@ -168,6 +174,13 @@ function Scanner({ onSelectStock, onSignal }) {
<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>

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
function SmartMoney() {
function SmartMoney({ onBuyClick }) {
const [data, setData] = useState(null);
useEffect(() => {
@@ -104,6 +104,13 @@ function SmartMoney() {
<td>{s.reason}</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 || s.current_price || 0)}
>
Buy
</button>
<button className="action-btn" onClick={() => openChart(s.symbol)}>Chart</button>
{s.sector && (
<button

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
function TrendScanner() {
function TrendScanner({ onBuyClick }) {
const [data, setData] = useState(null);
useEffect(() => {
@@ -81,6 +81,13 @@ function TrendScanner() {
<td>{s.trend_reason}</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 || s.current_price || 0)}
>
Buy
</button>
<button className="action-btn" onClick={() => openChart(s.symbol)}>Chart</button>
{s.sector && (
<button

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
function WatchlistManager({ userProfile, onProfileUpdated }) {
function WatchlistManager({ userProfile, onProfileUpdated, onBuyClick }) {
const [sectorMap, setSectorMap] = useState({});
const [newSectorName, setNewSectorName] = useState("");
const [searchQuery, setSearchQuery] = useState("");
@@ -116,7 +116,7 @@ function WatchlistManager({ userProfile, onProfileUpdated }) {
};
return (
<div className="animate-fade-in" style={{ display: 'flex', gap: '20px', marginTop: '20px', alignItems: 'flex-start' }}>
<div className="animate-fade-in" style={{ display: 'flex', flexWrap: 'wrap', gap: '20px', marginTop: '20px', alignItems: 'flex-start' }}>
{/* LEFT PANEL: Sector List & Creator */}
<div className="glass-card" style={{ flex: '1', minWidth: '250px' }}>
@@ -256,16 +256,24 @@ function WatchlistManager({ userProfile, onProfileUpdated }) {
</button>
</div>
{/* Bottom Row: + Personal */}
{/* Bottom Row: Actions */}
<div style={{ display: 'flex', gap: '10px' }}>
{selectedSector !== "Personal" && (
<button
onClick={() => { addStockToSector(symbol, "Personal"); }}
title="Add to Personal"
style={{ width: '100%', background: 'rgba(16, 185, 129, 0.1)', border: '1px solid var(--accent-green)', color: 'var(--accent-green)', borderRadius: '4px', cursor: 'pointer', padding: '6px', fontSize: '0.85rem' }}
style={{ flex: 1, background: 'rgba(16, 185, 129, 0.1)', border: '1px solid var(--accent-green)', color: 'var(--accent-green)', borderRadius: '4px', cursor: 'pointer', padding: '6px', fontSize: '0.85rem' }}
>
+ Add to Personal
+ Personal
</button>
)}
<button
onClick={() => onBuyClick(symbol, 0)}
style={{ flex: 1, background: 'var(--accent-green)', color: '#1a1a2e', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: '6px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Buy
</button>
</div>
</div>
))}
</div>

View File

@@ -214,3 +214,54 @@ h2 {
.text-orange { color: var(--accent-orange); }
.text-blue { color: var(--accent-blue); }
.text-muted { color: var(--text-muted); }
/* RESPONSIVE DESIGN */
@media (max-width: 768px) {
.app-container {
padding: 20px 10px;
}
.nav-container {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding-bottom: 10px;
margin-bottom: 25px;
}
.nav-btn {
white-space: nowrap;
flex-shrink: 0;
}
.glass-card {
padding: 16px;
}
h1 { font-size: 2rem; }
h2 { font-size: 1.3rem; }
.data-table th, .data-table td {
padding: 10px 8px;
font-size: 0.85rem;
}
/* Adjust grid columns for extreme small devices */
.stock-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.app-container {
padding: 15px 8px;
}
h1 { font-size: 1.8rem; }
.action-btn {
padding: 4px 8px;
font-size: 0.75rem;
}
}