commit - mock buy
This commit is contained in:
Binary file not shown.
BIN
backend/app/api/__pycache__/portfolio.cpython-313.pyc
Normal file
BIN
backend/app/api/__pycache__/portfolio.cpython-313.pyc
Normal file
Binary file not shown.
102
backend/app/api/portfolio.py
Normal file
102
backend/app/api/portfolio.py
Normal 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"}
|
||||||
Binary file not shown.
@@ -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
|
||||||
|
|||||||
Binary file not shown.
@@ -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)
|
||||||
|
|||||||
@@ -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
50
build_and_push.sh
Executable 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 "=========================================="
|
||||||
@@ -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
18
run_backend.sh
Executable 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
|
||||||
112
src/App.js
112
src/App.js
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user