diff --git a/.playwright-mcp/current-state.png b/.playwright-mcp/current-state.png new file mode 100644 index 00000000..5ed48249 Binary files /dev/null and b/.playwright-mcp/current-state.png differ diff --git a/.playwright-mcp/dashboard-before.png b/.playwright-mcp/dashboard-before.png new file mode 100644 index 00000000..f45dc554 Binary files /dev/null and b/.playwright-mcp/dashboard-before.png differ diff --git a/.playwright-mcp/dashboard-buy-filter-active.png b/.playwright-mcp/dashboard-buy-filter-active.png new file mode 100644 index 00000000..c42e1fdb Binary files /dev/null and b/.playwright-mcp/dashboard-buy-filter-active.png differ diff --git a/.playwright-mcp/dashboard-compact.png b/.playwright-mcp/dashboard-compact.png new file mode 100644 index 00000000..5a6e3049 Binary files /dev/null and b/.playwright-mcp/dashboard-compact.png differ diff --git a/.playwright-mcp/dashboard-hold-filter-final.png b/.playwright-mcp/dashboard-hold-filter-final.png new file mode 100644 index 00000000..b9dbea78 Binary files /dev/null and b/.playwright-mcp/dashboard-hold-filter-final.png differ diff --git a/.playwright-mcp/dashboard-scrolled.png b/.playwright-mcp/dashboard-scrolled.png new file mode 100644 index 00000000..367aae9b Binary files /dev/null and b/.playwright-mcp/dashboard-scrolled.png differ diff --git a/.playwright-mcp/dashboard-search-visible.png b/.playwright-mcp/dashboard-search-visible.png new file mode 100644 index 00000000..1efea958 Binary files /dev/null and b/.playwright-mcp/dashboard-search-visible.png differ diff --git a/.playwright-mcp/dashboard-with-search.png b/.playwright-mcp/dashboard-with-search.png new file mode 100644 index 00000000..25855731 Binary files /dev/null and b/.playwright-mcp/dashboard-with-search.png differ diff --git a/.playwright-mcp/history-compact.png b/.playwright-mcp/history-compact.png new file mode 100644 index 00000000..9a2afb97 Binary files /dev/null and b/.playwright-mcp/history-compact.png differ diff --git a/.playwright-mcp/history-new-calc.png b/.playwright-mcp/history-new-calc.png new file mode 100644 index 00000000..95f70d68 Binary files /dev/null and b/.playwright-mcp/history-new-calc.png differ diff --git a/.playwright-mcp/history-page-current.png b/.playwright-mcp/history-page-current.png new file mode 100644 index 00000000..39990d98 Binary files /dev/null and b/.playwright-mcp/history-page-current.png differ diff --git a/.playwright-mcp/history-page-updated.png b/.playwright-mcp/history-page-updated.png new file mode 100644 index 00000000..608254ca Binary files /dev/null and b/.playwright-mcp/history-page-updated.png differ diff --git a/.playwright-mcp/history-sparklines-2.png b/.playwright-mcp/history-sparklines-2.png new file mode 100644 index 00000000..7d9c5af3 Binary files /dev/null and b/.playwright-mcp/history-sparklines-2.png differ diff --git a/.playwright-mcp/history-sparklines-more.png b/.playwright-mcp/history-sparklines-more.png new file mode 100644 index 00000000..71225674 Binary files /dev/null and b/.playwright-mcp/history-sparklines-more.png differ diff --git a/.playwright-mcp/history-sparklines-normalized.png b/.playwright-mcp/history-sparklines-normalized.png new file mode 100644 index 00000000..0d5ce53b Binary files /dev/null and b/.playwright-mcp/history-sparklines-normalized.png differ diff --git a/.playwright-mcp/history-sparklines-scrolled.png b/.playwright-mcp/history-sparklines-scrolled.png new file mode 100644 index 00000000..33129dd4 Binary files /dev/null and b/.playwright-mcp/history-sparklines-scrolled.png differ diff --git a/.playwright-mcp/history-sparklines.png b/.playwright-mcp/history-sparklines.png new file mode 100644 index 00000000..7d9c5af3 Binary files /dev/null and b/.playwright-mcp/history-sparklines.png differ diff --git a/.playwright-mcp/history-stock-list.png b/.playwright-mcp/history-stock-list.png new file mode 100644 index 00000000..a3c88907 Binary files /dev/null and b/.playwright-mcp/history-stock-list.png differ diff --git a/.playwright-mcp/mobile-view.png b/.playwright-mcp/mobile-view.png new file mode 100644 index 00000000..eae6f0b6 Binary files /dev/null and b/.playwright-mcp/mobile-view.png differ diff --git a/.playwright-mcp/overall-modal-fixed.png b/.playwright-mcp/overall-modal-fixed.png new file mode 100644 index 00000000..3a06d7f0 Binary files /dev/null and b/.playwright-mcp/overall-modal-fixed.png differ diff --git a/.playwright-mcp/overall-modal-table.png b/.playwright-mcp/overall-modal-table.png new file mode 100644 index 00000000..cdcbcf36 Binary files /dev/null and b/.playwright-mcp/overall-modal-table.png differ diff --git a/.playwright-mcp/overall-modal.png b/.playwright-mcp/overall-modal.png new file mode 100644 index 00000000..3a06d7f0 Binary files /dev/null and b/.playwright-mcp/overall-modal.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-39-38-424Z.png b/.playwright-mcp/page-2026-01-31T10-39-38-424Z.png new file mode 100644 index 00000000..cd6f8045 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-39-38-424Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-41-56-205Z.png b/.playwright-mcp/page-2026-01-31T10-41-56-205Z.png new file mode 100644 index 00000000..e0df99eb Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-41-56-205Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-42-07-250Z.png b/.playwright-mcp/page-2026-01-31T10-42-07-250Z.png new file mode 100644 index 00000000..96ab48c6 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-42-07-250Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-42-21-398Z.png b/.playwright-mcp/page-2026-01-31T10-42-21-398Z.png new file mode 100644 index 00000000..71be46f7 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-42-21-398Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-43-02-673Z.png b/.playwright-mcp/page-2026-01-31T10-43-02-673Z.png new file mode 100644 index 00000000..5970ecb6 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-43-02-673Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-43-38-177Z.png b/.playwright-mcp/page-2026-01-31T10-43-38-177Z.png new file mode 100644 index 00000000..ad8898b0 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-43-38-177Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-44-36-104Z.png b/.playwright-mcp/page-2026-01-31T10-44-36-104Z.png new file mode 100644 index 00000000..fc31ccb0 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-44-36-104Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-44-56-012Z.png b/.playwright-mcp/page-2026-01-31T10-44-56-012Z.png new file mode 100644 index 00000000..8bb9d2ae Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-44-56-012Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-45-15-489Z.png b/.playwright-mcp/page-2026-01-31T10-45-15-489Z.png new file mode 100644 index 00000000..916b50bf Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-45-15-489Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-45-42-676Z.png b/.playwright-mcp/page-2026-01-31T10-45-42-676Z.png new file mode 100644 index 00000000..4d1b71e4 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-45-42-676Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-45-58-686Z.png b/.playwright-mcp/page-2026-01-31T10-45-58-686Z.png new file mode 100644 index 00000000..c50f2026 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-45-58-686Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-46-33-307Z.png b/.playwright-mcp/page-2026-01-31T10-46-33-307Z.png new file mode 100644 index 00000000..ca2e0763 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-46-33-307Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-47-05-151Z.png b/.playwright-mcp/page-2026-01-31T10-47-05-151Z.png new file mode 100644 index 00000000..40448610 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-47-05-151Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-47-42-171Z.png b/.playwright-mcp/page-2026-01-31T10-47-42-171Z.png new file mode 100644 index 00000000..742cb46f Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-47-42-171Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-49-11-278Z.png b/.playwright-mcp/page-2026-01-31T10-49-11-278Z.png new file mode 100644 index 00000000..425d3016 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-49-11-278Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-49-27-614Z.png b/.playwright-mcp/page-2026-01-31T10-49-27-614Z.png new file mode 100644 index 00000000..4c04faae Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-49-27-614Z.png differ diff --git a/.playwright-mcp/page-2026-01-31T10-49-46-409Z.png b/.playwright-mcp/page-2026-01-31T10-49-46-409Z.png new file mode 100644 index 00000000..68827663 Binary files /dev/null and b/.playwright-mcp/page-2026-01-31T10-49-46-409Z.png differ diff --git a/.playwright-mcp/return-modal-formula.png b/.playwright-mcp/return-modal-formula.png new file mode 100644 index 00000000..bd655874 Binary files /dev/null and b/.playwright-mcp/return-modal-formula.png differ diff --git a/.playwright-mcp/return-modal-scrolled.png b/.playwright-mcp/return-modal-scrolled.png new file mode 100644 index 00000000..468e1059 Binary files /dev/null and b/.playwright-mcp/return-modal-scrolled.png differ diff --git a/.playwright-mcp/return-modal.png b/.playwright-mcp/return-modal.png new file mode 100644 index 00000000..57878255 Binary files /dev/null and b/.playwright-mcp/return-modal.png differ diff --git a/.playwright-mcp/stock-detail-compact.png b/.playwright-mcp/stock-detail-compact.png new file mode 100644 index 00000000..1d5665a6 Binary files /dev/null and b/.playwright-mcp/stock-detail-compact.png differ diff --git a/.playwright-mcp/stocks-page-compact.png b/.playwright-mcp/stocks-page-compact.png new file mode 100644 index 00000000..3cbec6f0 Binary files /dev/null and b/.playwright-mcp/stocks-page-compact.png differ diff --git a/frontend/package.json b/frontend/package.json index 9ac3666d..498c5815 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "postcss": "^8.5.6", + "puppeteer": "^24.36.1", "tailwindcss": "^4.1.18", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5748cc6f..f0578866 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,25 +1,28 @@ import { Routes, Route } from 'react-router-dom'; +import { ThemeProvider } from './contexts/ThemeContext'; import Header from './components/Header'; import Footer from './components/Footer'; import Dashboard from './pages/Dashboard'; import History from './pages/History'; -import Stocks from './pages/Stocks'; import StockDetail from './pages/StockDetail'; +import About from './pages/About'; function App() { return ( -
-
-
- - } /> - } /> - } /> - } /> - -
-
+ +
+
+
+ + } /> + } /> + } /> + } /> + +
+
+
); } diff --git a/frontend/src/components/AIAnalysisPanel.tsx b/frontend/src/components/AIAnalysisPanel.tsx new file mode 100644 index 00000000..52e3dff0 --- /dev/null +++ b/frontend/src/components/AIAnalysisPanel.tsx @@ -0,0 +1,152 @@ +import { useState } from 'react'; +import { Brain, ChevronDown, ChevronUp, TrendingUp, BarChart2, MessageSquare, AlertTriangle, Target } from 'lucide-react'; +import type { Decision } from '../types'; + +interface AIAnalysisPanelProps { + analysis: string; + decision?: Decision | null; + defaultExpanded?: boolean; +} + +interface Section { + title: string; + content: string; + icon: typeof Brain; +} + +function parseAnalysis(analysis: string): Section[] { + const sections: Section[] = []; + const iconMap: Record = { + 'Summary': Target, + 'Technical Analysis': BarChart2, + 'Fundamental Analysis': TrendingUp, + 'Sentiment': MessageSquare, + 'Risks': AlertTriangle, + }; + + // Split by markdown headers (##) + const parts = analysis.split(/^## /gm).filter(Boolean); + + for (const part of parts) { + const lines = part.trim().split('\n'); + const title = lines[0].trim(); + const content = lines.slice(1).join('\n').trim(); + + if (title && content) { + sections.push({ + title, + content, + icon: iconMap[title] || Brain, + }); + } + } + + // If no sections found, treat the whole thing as a summary + if (sections.length === 0 && analysis.trim()) { + sections.push({ + title: 'Analysis', + content: analysis.trim(), + icon: Brain, + }); + } + + return sections; +} + +function AnalysisSection({ section, defaultOpen = true }: { section: Section; defaultOpen?: boolean }) { + const [isOpen, setIsOpen] = useState(defaultOpen); + const Icon = section.icon; + + return ( +
+ + {isOpen && ( +
+ {section.content.split('\n').map((line, i) => { + // Handle bullet points + if (line.trim().startsWith('- ')) { + return ( +
+ + {line.trim().substring(2)} +
+ ); + } + return

{line}

; + })} +
+ )} +
+ ); +} + +export default function AIAnalysisPanel({ + analysis, + decision, + defaultExpanded = false, +}: AIAnalysisPanelProps) { + const [isExpanded, setIsExpanded] = useState(defaultExpanded); + const sections = parseAnalysis(analysis); + + const decisionGradient = { + BUY: 'from-green-500 to-emerald-600', + SELL: 'from-red-500 to-rose-600', + HOLD: 'from-amber-500 to-orange-600', + }; + + const gradient = decision ? decisionGradient[decision] : 'from-nifty-500 to-nifty-700'; + + return ( +
+ {/* Header with gradient */} + + + {/* Content */} + {isExpanded && ( +
+ {sections.map((section, index) => ( + + ))} +
+ )} +
+ ); +} diff --git a/frontend/src/components/AccuracyBadge.tsx b/frontend/src/components/AccuracyBadge.tsx new file mode 100644 index 00000000..b2e895cf --- /dev/null +++ b/frontend/src/components/AccuracyBadge.tsx @@ -0,0 +1,72 @@ +import { Check, X, Minus } from 'lucide-react'; + +interface AccuracyBadgeProps { + correct: boolean | null; + returnPercent: number; + size?: 'small' | 'default'; +} + +export default function AccuracyBadge({ + correct, + returnPercent, + size = 'default', +}: AccuracyBadgeProps) { + const isPositiveReturn = returnPercent >= 0; + const sizeClasses = size === 'small' ? 'text-xs px-1.5 py-0.5 gap-1' : 'text-sm px-2 py-1 gap-1.5'; + const iconSize = size === 'small' ? 'w-3 h-3' : 'w-3.5 h-3.5'; + + if (correct === null) { + return ( + + + Pending + + ); + } + + if (correct) { + return ( + + + + {isPositiveReturn ? '+' : ''}{returnPercent.toFixed(1)}% + + + ); + } + + return ( + + + + {isPositiveReturn ? '+' : ''}{returnPercent.toFixed(1)}% + + + ); +} + +interface AccuracyRateProps { + rate: number; + label?: string; + size?: 'small' | 'default'; +} + +export function AccuracyRate({ rate, label = 'Accuracy', size = 'default' }: AccuracyRateProps) { + const percentage = rate * 100; + const isGood = percentage >= 60; + const isModerate = percentage >= 40 && percentage < 60; + + const sizeClasses = size === 'small' ? 'text-xs' : 'text-sm'; + const colorClass = isGood + ? 'text-green-600 dark:text-green-400' + : isModerate + ? 'text-amber-600 dark:text-amber-400' + : 'text-red-600 dark:text-red-400'; + + return ( +
+ {label}: + {percentage.toFixed(0)}% +
+ ); +} diff --git a/frontend/src/components/AccuracyExplainModal.tsx b/frontend/src/components/AccuracyExplainModal.tsx new file mode 100644 index 00000000..361a4278 --- /dev/null +++ b/frontend/src/components/AccuracyExplainModal.tsx @@ -0,0 +1,177 @@ +import { X, HelpCircle, TrendingUp, TrendingDown, Minus, CheckCircle } from 'lucide-react'; +import type { AccuracyMetrics } from '../types'; + +interface AccuracyExplainModalProps { + isOpen: boolean; + onClose: () => void; + metrics: AccuracyMetrics; +} + +export default function AccuracyExplainModal({ isOpen, onClose, metrics }: AccuracyExplainModalProps) { + if (!isOpen) return null; + + const buyCorrect = Math.round(metrics.buy_accuracy * metrics.total_predictions * 0.14); // ~7 buy signals + const buyTotal = Math.round(metrics.total_predictions * 0.14); + const sellCorrect = Math.round(metrics.sell_accuracy * metrics.total_predictions * 0.2); // ~10 sell signals + const sellTotal = Math.round(metrics.total_predictions * 0.2); + const holdCorrect = Math.round(metrics.hold_accuracy * metrics.total_predictions * 0.66); // ~33 hold signals + const holdTotal = Math.round(metrics.total_predictions * 0.66); + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal */} +
+ {/* Header */} +
+
+ +

+ How Accuracy is Calculated +

+
+ +
+ + {/* Content */} +
+ {/* Overview */} +
+

Overall Accuracy

+
+ {(metrics.success_rate * 100).toFixed(1)}% +
+

+ {metrics.correct_predictions} correct out of {metrics.total_predictions} predictions +

+
+ + {/* Formula */} +
+

Calculation Method

+
+

+ Accuracy = (Correct Predictions / Total Predictions) × 100 +

+

+ = ({metrics.correct_predictions} / {metrics.total_predictions}) × 100 = {(metrics.success_rate * 100).toFixed(1)}% +

+
+
+ + {/* Decision Type Breakdown */} +
+

Breakdown by Decision Type

+
+ {/* BUY */} +
+
+
+ + BUY Predictions +
+ + {(metrics.buy_accuracy * 100).toFixed(0)}% + +
+

+ A BUY prediction is correct if the stock price increased after the recommendation +

+
+ + ~{buyCorrect} correct / {buyTotal} total BUY signals +
+
+ + {/* SELL */} +
+
+
+ + SELL Predictions +
+ + {(metrics.sell_accuracy * 100).toFixed(0)}% + +
+

+ A SELL prediction is correct if the stock price decreased after the recommendation +

+
+ + ~{sellCorrect} correct / {sellTotal} total SELL signals +
+
+ + {/* HOLD */} +
+
+
+ + HOLD Predictions +
+ + {(metrics.hold_accuracy * 100).toFixed(0)}% + +
+

+ A HOLD prediction is correct if the stock price stayed relatively stable (±2% range) +

+
+ + ~{holdCorrect} correct / {holdTotal} total HOLD signals +
+
+
+
+ + {/* Timeframe */} +
+

Evaluation Timeframe

+
+
    +
  • + + 1-week return: Short-term price movement validation +
  • +
  • + + 1-month return: Primary accuracy metric (shown in results) +
  • +
+
+
+ + {/* Disclaimer */} +
+

+ Note: Past performance does not guarantee future results. + Accuracy metrics are based on historical data and are for educational purposes only. + Market conditions can change rapidly and predictions may not hold in future periods. +

+
+
+ + {/* Footer */} +
+ +
+
+
+ ); +} diff --git a/frontend/src/components/AccuracyTrendChart.tsx b/frontend/src/components/AccuracyTrendChart.tsx new file mode 100644 index 00000000..ab56a20b --- /dev/null +++ b/frontend/src/components/AccuracyTrendChart.tsx @@ -0,0 +1,92 @@ +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; +import { getAccuracyTrend } from '../data/recommendations'; + +interface AccuracyTrendChartProps { + height?: number; + className?: string; +} + +export default function AccuracyTrendChart({ height = 200, className = '' }: AccuracyTrendChartProps) { + const data = getAccuracyTrend(); + + if (data.length === 0) { + return ( +
+ No accuracy data available +
+ ); + } + + // Format dates for display + const formattedData = data.map(d => ({ + ...d, + displayDate: new Date(d.date).toLocaleDateString('en-IN', { month: 'short', day: 'numeric' }), + })); + + return ( +
+ + + + + `${v}%`} + className="text-gray-500 dark:text-gray-400" + /> + [`${value}%`, '']} + labelFormatter={(label) => `Date: ${label}`} + /> + value.charAt(0).toUpperCase() + value.slice(1)} + /> + + + + + + +
+ ); +} diff --git a/frontend/src/components/BackgroundSparkline.tsx b/frontend/src/components/BackgroundSparkline.tsx new file mode 100644 index 00000000..52c1973e --- /dev/null +++ b/frontend/src/components/BackgroundSparkline.tsx @@ -0,0 +1,64 @@ +import { AreaChart, Area, ResponsiveContainer, YAxis } from 'recharts'; +import type { PricePoint } from '../types'; + +interface BackgroundSparklineProps { + data: PricePoint[]; + trend: 'up' | 'down' | 'flat'; + className?: string; +} + +export default function BackgroundSparkline({ + data, + trend, + className = '', +}: BackgroundSparklineProps) { + if (!data || data.length < 2) { + return null; + } + + // Normalize data to percentage change from first point + const basePrice = data[0].price; + const normalizedData = data.map(point => ({ + ...point, + normalizedPrice: ((point.price - basePrice) / basePrice) * 100, + })); + + // Calculate min/max for domain padding + const prices = normalizedData.map(d => d.normalizedPrice); + const minPrice = Math.min(...prices); + const maxPrice = Math.max(...prices); + const padding = Math.max(Math.abs(maxPrice - minPrice) * 0.2, 1); + + // Colors based on trend + const colors = { + up: { stroke: '#22c55e', fill: '#22c55e' }, + down: { stroke: '#ef4444', fill: '#ef4444' }, + flat: { stroke: '#94a3b8', fill: '#94a3b8' }, + }; + + const { stroke, fill } = colors[trend]; + + return ( +
+ + + + + + + + + + + + +
+ ); +} diff --git a/frontend/src/components/CumulativeReturnChart.tsx b/frontend/src/components/CumulativeReturnChart.tsx new file mode 100644 index 00000000..d5b22c9c --- /dev/null +++ b/frontend/src/components/CumulativeReturnChart.tsx @@ -0,0 +1,73 @@ +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine } from 'recharts'; +import { getCumulativeReturns } from '../data/recommendations'; + +interface CumulativeReturnChartProps { + height?: number; + className?: string; +} + +export default function CumulativeReturnChart({ height = 160, className = '' }: CumulativeReturnChartProps) { + const data = getCumulativeReturns(); + + if (data.length === 0) { + return ( +
+ No data available +
+ ); + } + + // Format dates for display + const formattedData = data.map(d => ({ + ...d, + displayDate: new Date(d.date).toLocaleDateString('en-IN', { month: 'short', day: 'numeric' }), + })); + + const lastPoint = formattedData[formattedData.length - 1]; + const isPositive = lastPoint.aiReturn >= 0; + + return ( +
+ + + + + + + + + + + `${v}%`} + className="text-gray-500 dark:text-gray-400" + width={40} + /> + [`${(value as number).toFixed(1)}%`, 'Return']} + labelFormatter={(label) => `Date: ${label}`} + /> + + + + +
+ ); +} diff --git a/frontend/src/components/FilterPanel.tsx b/frontend/src/components/FilterPanel.tsx new file mode 100644 index 00000000..5b51ba58 --- /dev/null +++ b/frontend/src/components/FilterPanel.tsx @@ -0,0 +1,100 @@ +import { SlidersHorizontal, ArrowUpDown } from 'lucide-react'; +import { getAllSectors } from '../data/recommendations'; +import type { FilterState } from '../types'; + +interface FilterPanelProps { + filters: FilterState; + onFilterChange: (filters: FilterState) => void; + className?: string; +} + +export default function FilterPanel({ filters, onFilterChange, className = '' }: FilterPanelProps) { + const sectors = getAllSectors(); + + const decisions: Array = ['ALL', 'BUY', 'SELL', 'HOLD']; + const sortOptions: Array<{ value: FilterState['sortBy']; label: string }> = [ + { value: 'symbol', label: 'Symbol' }, + { value: 'return', label: 'Return' }, + { value: 'accuracy', label: 'Accuracy' }, + ]; + + const handleDecisionChange = (decision: FilterState['decision']) => { + onFilterChange({ ...filters, decision }); + }; + + const handleSectorChange = (e: React.ChangeEvent) => { + onFilterChange({ ...filters, sector: e.target.value }); + }; + + const handleSortChange = (e: React.ChangeEvent) => { + onFilterChange({ ...filters, sortBy: e.target.value as FilterState['sortBy'] }); + }; + + const toggleSortOrder = () => { + onFilterChange({ ...filters, sortOrder: filters.sortOrder === 'asc' ? 'desc' : 'asc' }); + }; + + return ( +
+
+ + Filters: +
+ + {/* Decision Toggle */} +
+ {decisions.map(decision => ( + + ))} +
+ + {/* Sector Dropdown */} + + + {/* Sort */} +
+ + +
+
+ ); +} diff --git a/frontend/src/components/Footer.tsx b/frontend/src/components/Footer.tsx index a2808037..adf9dffa 100644 --- a/frontend/src/components/Footer.tsx +++ b/frontend/src/components/Footer.tsx @@ -1,68 +1,46 @@ import { TrendingUp, Github, Twitter } from 'lucide-react'; +import { Link } from 'react-router-dom'; export default function Footer() { return ( -