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 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" SECRET_KEY = "stock-scanner-super-secret-key-12345"
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 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 from app.db.database import Base
import datetime
class User(Base): class User(Base):
__tablename__ = "users" __tablename__ = "users"
@@ -12,3 +13,13 @@ class User(Base):
mobile_no = Column(String, nullable=True) mobile_no = Column(String, nullable=True)
gender = Column(String, nullable=True) gender = Column(String, nullable=True)
sector_map = Column(JSON, 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 import FastAPI
from fastapi.middleware.cors import CORSMiddleware 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 from app.db.database import engine, Base
# Initialize the database tables # Initialize the database tables
@@ -20,6 +20,7 @@ app.add_middleware(
# Include Routers # Include Routers
app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(scanner.router, prefix="/api/scanner", tags=["scanner"]) app.include_router(scanner.router, prefix="/api/scanner", tags=["scanner"])
app.include_router(portfolio.router, prefix="/api/portfolio", tags=["portfolio"])
@app.get("/") @app.get("/")
def read_root(): 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: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
environment: 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 [userProfile, setUserProfile] = useState(null);
const [dropdownOpen, setDropdownOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false);
const [scannerLoaded, setScannerLoaded] = useState(false); const [scannerLoaded, setScannerLoaded] = useState(false);
const [buyModalData, setBuyModalData] = useState(null);
const [buyQuantity, setBuyQuantity] = useState(1);
const navigate = useNavigate(); const navigate = useNavigate();
const fetchProfile = () => { const fetchProfile = () => {
@@ -43,34 +45,55 @@ function Dashboard() {
navigate("/login"); 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 ( return (
<div className="app-container"> <div className="app-container">
{/* HEADER */} {/* HEADER */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', position: 'relative' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', position: 'relative' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
{/* Logo only, no text */}
<span style={{ fontSize: '2.5rem' }}>📊</span> <span style={{ fontSize: '2.5rem' }}>📊</span>
<h1 style={{ margin: 0 }}>Stock Scanner Pro</h1>
</div> </div>
{userProfile && ( {userProfile && (
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<div <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)} 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() : '?'} {userProfile.display_name ? userProfile.display_name.charAt(0).toUpperCase() : '?'}
</div> </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> <span style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}></span>
</div> </div>
{dropdownOpen && ( {dropdownOpen && (
<div className="glass-card animate-fade-in" style={{ position: 'absolute', top: '55px', right: '0', width: '220px', zIndex: 100, padding: '10px' }}> <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' }} onClick={() => { setView('profile'); setDropdownOpen(false); }}> <button className="nav-btn" style={{ width: '100%', marginBottom: '8px', border: 'none', whiteSpace: 'nowrap', padding: '8px' }} onClick={() => { setView('profile'); setDropdownOpen(false); }}>
Profile Settings Profile Settings
</button> </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 🚪 Logout
</button> </button>
</div> </div>
@@ -79,14 +102,14 @@ function Dashboard() {
)} )}
</div> </div>
<div className="nav-container"> <div className="nav-container" style={{ gap: '8px' }}>
<button className={`nav-btn ${view === "dashboard" ? "active" : ""}`} onClick={() => setView("dashboard")} title="Dashboard" style={{ padding: '8px 12px' }}>🏠</button> <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")}>📊 Scanner</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")}>📈 Ribbon Strategy</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")}>🏦 Smart Money</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")}> Trend Analysis</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")}>📰 Market Intel</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")}>📋 Watchlists</button> <button className={`nav-btn ${view === "watchlists" ? "active" : ""}`} onClick={() => setView("watchlists")} style={{ padding: '6px 12px', fontSize: '0.9rem' }}>📋 Watchlists</button>
</div> </div>
<div className="animate-fade-in"> <div className="animate-fade-in">
@@ -95,6 +118,7 @@ function Dashboard() {
<Scanner <Scanner
onSelectStock={setSelectedStock} onSelectStock={setSelectedStock}
onSignal={setSignal} onSignal={setSignal}
onBuyClick={onBuyClick}
/> />
<div className="glass-card" style={{ marginTop: "20px" }}> <div className="glass-card" style={{ marginTop: "20px" }}>
<h2>TradingView Chart</h2> <h2>TradingView Chart</h2>
@@ -103,14 +127,60 @@ function Dashboard() {
{signal && <Popup signal={signal} />} {signal && <Popup signal={signal} />}
</> </>
)} )}
{view === "dashboard" && <MainDashboard userProfile={userProfile} />} {view === "dashboard" && <MainDashboard userProfile={userProfile} onBuyClick={onBuyClick} />}
{view === "ribbon" && <Ribbon />} {view === "ribbon" && <Ribbon onBuyClick={onBuyClick} />}
{view === "smartmoney" && <SmartMoney />} {view === "smartmoney" && <SmartMoney onBuyClick={onBuyClick} />}
{view === "marketintel" && <MarketIntel />} {view === "marketintel" && <MarketIntel onBuyClick={onBuyClick} />}
{view === "trend" && <TrendScanner />} {view === "trend" && <TrendScanner onBuyClick={onBuyClick} />}
{view === "watchlists" && <WatchlistManager userProfile={userProfile} onProfileUpdated={fetchProfile} />} {view === "watchlists" && <WatchlistManager userProfile={userProfile} onProfileUpdated={fetchProfile} onBuyClick={onBuyClick} />}
{view === "profile" && <Profile userProfile={userProfile} onProfileUpdated={fetchProfile} />} {view === "profile" && <Profile userProfile={userProfile} onProfileUpdated={fetchProfile} />}
</div> </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> </div>
); );
} }

View File

@@ -1,12 +1,40 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import axios from "axios"; import axios from "axios";
function MainDashboard({ userProfile }) { function MainDashboard({ userProfile, onBuyClick }) {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]); const [searchResults, setSearchResults] = useState([]);
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const searchTimeout = useRef(null); 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) => { const formatMarketCap = (cap) => {
if (!cap) return "N/A"; if (!cap) return "N/A";
if (cap >= 1e12) return "₹" + (cap / 1e12).toFixed(2) + "T"; if (cap >= 1e12) return "₹" + (cap / 1e12).toFixed(2) + "T";
@@ -134,6 +162,13 @@ function MainDashboard({ userProfile }) {
)} )}
</div> </div>
</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' }}> <div style={{ color: 'var(--accent-green)', display: 'flex', alignItems: 'center', gap: '5px' }}>
<span>Chart</span> <span>Chart</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <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> </svg>
</div> </div>
</div> </div>
</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> </div>
); );
} }

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
function Ribbon() { function Ribbon({ onBuyClick }) {
const [data, setData] = useState([]); const [data, setData] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -77,6 +77,13 @@ function Ribbon() {
</td> </td>
<td> <td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}> <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)}> <button className="action-btn" onClick={() => openChart(s.symbol)}>
Chart Chart
</button> </button>

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
import AboutScanner from "./AboutScanner"; import AboutScanner from "./AboutScanner";
function Scanner({ onSelectStock, onSignal }) { function Scanner({ onSelectStock, onSignal, onBuyClick }) {
const [data, setData] = useState(null); const [data, setData] = useState(null);
const [lastBest, setLastBest] = useState(null); const [lastBest, setLastBest] = useState(null);
@@ -68,7 +68,7 @@ function Scanner({ onSelectStock, onSignal }) {
{/* Top 5 Trades */} {/* Top 5 Trades */}
{(data.best_5 || []).length > 0 && ( {(data.best_5 || []).length > 0 && (
<div className="glass-card animate-fade-in" style={{ marginBottom: "30px", borderLeft: "4px solid var(--accent-green)" }}> <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"> <div className="data-table-container">
<table className="data-table"> <table className="data-table">
<thead> <thead>
@@ -104,6 +104,12 @@ function Scanner({ onSelectStock, onSignal }) {
<button className="action-btn" onClick={() => { openChart(s?.symbol); onSelectStock(s?.symbol); }}> <button className="action-btn" onClick={() => { openChart(s?.symbol); onSelectStock(s?.symbol); }}>
View Chart View Chart
</button> </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 && ( {s?.sector && (
<button <button
onClick={() => removeStockFromSector(s?.symbol, s.sector)} onClick={() => removeStockFromSector(s?.symbol, s.sector)}
@@ -168,6 +174,13 @@ function Scanner({ onSelectStock, onSignal }) {
<td>{s?.ai_score}</td> <td>{s?.ai_score}</td>
<td> <td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}> <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); }}> <button className="action-btn" onClick={() => { openChart(s?.symbol); onSelectStock(s?.symbol); }}>
Chart Chart
</button> </button>

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
function SmartMoney() { function SmartMoney({ onBuyClick }) {
const [data, setData] = useState(null); const [data, setData] = useState(null);
useEffect(() => { useEffect(() => {
@@ -104,6 +104,13 @@ function SmartMoney() {
<td>{s.reason}</td> <td>{s.reason}</td>
<td> <td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}> <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> <button className="action-btn" onClick={() => openChart(s.symbol)}>Chart</button>
{s.sector && ( {s.sector && (
<button <button

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
function TrendScanner() { function TrendScanner({ onBuyClick }) {
const [data, setData] = useState(null); const [data, setData] = useState(null);
useEffect(() => { useEffect(() => {
@@ -81,6 +81,13 @@ function TrendScanner() {
<td>{s.trend_reason}</td> <td>{s.trend_reason}</td>
<td> <td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}> <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> <button className="action-btn" onClick={() => openChart(s.symbol)}>Chart</button>
{s.sector && ( {s.sector && (
<button <button

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import axios from "axios"; import axios from "axios";
function WatchlistManager({ userProfile, onProfileUpdated }) { function WatchlistManager({ userProfile, onProfileUpdated, onBuyClick }) {
const [sectorMap, setSectorMap] = useState({}); const [sectorMap, setSectorMap] = useState({});
const [newSectorName, setNewSectorName] = useState(""); const [newSectorName, setNewSectorName] = useState("");
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
@@ -116,7 +116,7 @@ function WatchlistManager({ userProfile, onProfileUpdated }) {
}; };
return ( 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 */} {/* LEFT PANEL: Sector List & Creator */}
<div className="glass-card" style={{ flex: '1', minWidth: '250px' }}> <div className="glass-card" style={{ flex: '1', minWidth: '250px' }}>
@@ -256,16 +256,24 @@ function WatchlistManager({ userProfile, onProfileUpdated }) {
</button> </button>
</div> </div>
{/* Bottom Row: + Personal */} {/* Bottom Row: Actions */}
<div style={{ display: 'flex', gap: '10px' }}>
{selectedSector !== "Personal" && ( {selectedSector !== "Personal" && (
<button <button
onClick={() => { addStockToSector(symbol, "Personal"); }} onClick={() => { addStockToSector(symbol, "Personal"); }}
title="Add to 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>
)} )}
<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>
))} ))}
</div> </div>

View File

@@ -214,3 +214,54 @@ h2 {
.text-orange { color: var(--accent-orange); } .text-orange { color: var(--accent-orange); }
.text-blue { color: var(--accent-blue); } .text-blue { color: var(--accent-blue); }
.text-muted { color: var(--text-muted); } .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;
}
}