diff --git a/frontend/src/components/FilterComponents.tsx b/frontend/src/components/FilterComponents.tsx new file mode 100644 index 0000000..c38c07b --- /dev/null +++ b/frontend/src/components/FilterComponents.tsx @@ -0,0 +1,91 @@ +import { useScannerStore } from '../store/scannerStore'; +import type { StockUniverse } from '../services/stockApi'; + +function UniverseSelect() { + const { selectedUniverse, availableUniverses, setSelectedUniverse } = useScannerStore(); + + return ( +
+ + +
+ ); +} + +function FilterInput({ label, value, onChange, min = 0, step = 0.5, max }: { + label: string; + value: number; + onChange: (value: number) => void; + min?: number; + max?: number; + step?: number; +}) { + return ( +
+ + onChange(Number(e.target.value))} + className="w-full px-3 py-2 rounded text-sm font-mono border focus:outline-none focus:ring-1" + style={{ backgroundColor: 'var(--bg-tertiary)', borderColor: 'var(--border-color)', color: 'var(--text-primary)' }} + min={min} + max={max} + step={step} + /> +
+ ); +} + +function FilterInputs() { + const { filters, updateFilters } = useScannerStore(); + + return ( + <> + updateFilters({ minPrice: v })} + step={0.5} + /> + updateFilters({ minVolumeDollars: v * 1000000 })} + min={0} + step={1} + /> + updateFilters({ minADR: v })} + step={0.5} + /> + updateFilters({ minScore: v })} + min={0} + max={20} + step={1} + /> + + ); +} + +export { UniverseSelect, FilterInputs, FilterInput }; \ No newline at end of file diff --git a/frontend/src/components/ResultsTable.tsx b/frontend/src/components/ResultsTable.tsx index 04b661f..407ee9a 100644 --- a/frontend/src/components/ResultsTable.tsx +++ b/frontend/src/components/ResultsTable.tsx @@ -31,6 +31,11 @@ export default function ResultsTable() { /> +
+
+
+
+
); } @@ -40,8 +45,11 @@ export default function ResultsTable() {
[]
-

- Select a universe and run scan to find momentum stocks +

+ No signals found +

+

+ Try lowering the min score or expanding your universe

diff --git a/frontend/src/components/ScannerControls.tsx b/frontend/src/components/ScannerControls.tsx index 7977afb..7793f50 100644 --- a/frontend/src/components/ScannerControls.tsx +++ b/frontend/src/components/ScannerControls.tsx @@ -1,15 +1,13 @@ import { useEffect, useRef, useCallback } from 'react'; import { useScannerStore } from '../store/scannerStore'; -import { STOCK_UNIVERSES } from '../services/stockApi'; import { calculateMetrics, filterAndSortResults } from '../utils/calculations'; -import { fetchBatchHistoricalData } from '../services/stockApi'; +import { fetchBatchHistoricalData, STOCK_UNIVERSES, type StockUniverse } from '../services/stockApi'; +import { UniverseSelect, FilterInputs } from './FilterComponents'; export default function ScannerControls() { const { selectedUniverse, - setSelectedUniverse, filters, - updateFilters, setIsLoading, setError, setProgress, @@ -18,6 +16,7 @@ export default function ScannerControls() { error, results, resetScan, + loadUniverses, } = useScannerStore(); const hasAutoScanned = useRef(false); @@ -27,7 +26,7 @@ export default function ScannerControls() { setIsLoading(true); try { - const universe = STOCK_UNIVERSES.find(u => u.name === selectedUniverse); + const universe = STOCK_UNIVERSES.find((u: StockUniverse) => u.name === selectedUniverse); if (!universe) { throw new Error('Please select a valid stock universe'); } @@ -60,6 +59,10 @@ export default function ScannerControls() { } }, [selectedUniverse, filters, resetScan, setIsLoading, setError, setProgress, setResults]); + useEffect(() => { + loadUniverses(); + }, [loadUniverses]); + useEffect(() => { if (!hasAutoScanned.current && results.length === 0 && !isLoading) { hasAutoScanned.current = true; @@ -82,84 +85,8 @@ export default function ScannerControls() {
-
- - -
- -
- - updateFilters({ minPrice: Number(e.target.value) })} - className="w-full px-3 py-2 rounded text-sm font-mono border focus:outline-none focus:ring-1" - style={{ backgroundColor: 'var(--bg-tertiary)', borderColor: 'var(--border-color)', color: 'var(--text-primary)' }} - min="0" - step="0.5" - /> -
- -
- - updateFilters({ minVolumeDollars: Number(e.target.value) * 1000000 })} - className="w-full px-3 py-2 rounded text-sm font-mono border focus:outline-none focus:ring-1" - style={{ backgroundColor: 'var(--bg-tertiary)', borderColor: 'var(--border-color)', color: 'var(--text-primary)' }} - min="0" - step="1" - /> -
- -
- - updateFilters({ minADR: Number(e.target.value) })} - className="w-full px-3 py-2 rounded text-sm font-mono border focus:outline-none focus:ring-1" - style={{ backgroundColor: 'var(--bg-tertiary)', borderColor: 'var(--border-color)', color: 'var(--text-primary)' }} - min="0" - step="0.5" - /> -
- -
- - updateFilters({ minScore: Number(e.target.value) })} - className="w-full px-3 py-2 rounded text-sm font-mono border focus:outline-none focus:ring-1" - style={{ backgroundColor: 'var(--bg-tertiary)', borderColor: 'var(--border-color)', color: 'var(--text-primary)' }} - min="0" - max="20" - step="1" - /> -
+ +