mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-06 20:37:56 +02:00
feat: wire TypeScript interfaces into all component props, fix 12 lint errors
Former-commit-id: 04b30a9e7af32b644140c45333f55c20afec45f2
This commit is contained in:
@@ -3,8 +3,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { ArrowUpRight, ArrowDownRight, TrendingUp, Droplet, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import type { DashboardData } from "@/types/dashboard";
|
||||
|
||||
const MarketsPanel = React.memo(function MarketsPanel({ data }: { data: any }) {
|
||||
const MarketsPanel = React.memo(function MarketsPanel({ data }: { data: DashboardData }) {
|
||||
const [isMinimized, setIsMinimized] = useState(true);
|
||||
|
||||
const stocks = data?.stocks || {};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AlertTriangle, Clock, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import React, { useEffect, useRef, useCallback } from 'react';
|
||||
import Hls from 'hls.js';
|
||||
import WikiImage from '@/components/WikiImage';
|
||||
import type { DashboardData, SelectedEntity, RegionDossier } from "@/types/dashboard";
|
||||
|
||||
// HLS video player — uses hls.js on Chrome/Firefox, native on Safari
|
||||
function HlsVideo({ url, className }: { url: string; className?: string }) {
|
||||
@@ -154,7 +155,7 @@ const VESSEL_TYPE_WIKI: Record<string, string> = {
|
||||
'military_vessel': 'https://en.wikipedia.org/wiki/Warship',
|
||||
};
|
||||
|
||||
function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoading }: { data: any, selectedEntity?: { type: string, id: string | number, name?: string, callsign?: string, media_url?: string, extra?: any } | null, regionDossier?: any, regionDossierLoading?: boolean }) {
|
||||
function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoading }: { data: DashboardData, selectedEntity?: SelectedEntity | null, regionDossier?: RegionDossier | null, regionDossierLoading?: boolean }) {
|
||||
const [isMinimized, setIsMinimized] = useState(false);
|
||||
const [expandedIndexes, setExpandedIndexes] = useState<number[]>([]);
|
||||
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
@@ -431,7 +432,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi
|
||||
airline = "PRIVATE JET";
|
||||
} else if (selectedEntity.type === 'private_flight') {
|
||||
airline = "PRIVATE / GA";
|
||||
} else if (flight.airline_code) {
|
||||
} else if ('airline_code' in flight && flight.airline_code) {
|
||||
// Use the airline code resolved from adsb.lol routeset API
|
||||
const codeMap: Record<string, string> = {
|
||||
"UAL": "UNITED AIRLINES", "DAL": "DELTA AIR LINES", "SWA": "SOUTHWEST AIRLINES",
|
||||
@@ -603,7 +604,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi
|
||||
<span className="text-[var(--text-primary)] text-xs font-bold">{ship.callsign}</span>
|
||||
</div>
|
||||
)}
|
||||
{ship.imo > 0 && (
|
||||
{(ship.imo ?? 0) > 0 && (
|
||||
<div className="flex justify-between items-center border-b border-[var(--border-primary)] pb-2">
|
||||
<span className="text-[var(--text-muted)] text-[10px]">IMO NUMBER</span>
|
||||
<span className="text-[var(--text-primary)] text-xs font-bold">{ship.imo}</span>
|
||||
|
||||
@@ -4,11 +4,12 @@ import { API_BASE } from "@/lib/api";
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { RadioReceiver, Activity, Play, Square, FastForward, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import type { DashboardData, SelectedEntity, RadioFeed } from "@/types/dashboard";
|
||||
|
||||
export default function RadioInterceptPanel({ data, isEavesdropping, setIsEavesdropping, eavesdropLocation, cameraCenter, selectedEntity }: { data: any, isEavesdropping?: boolean, setIsEavesdropping?: (val: boolean) => void, eavesdropLocation?: { lat: number, lng: number } | null, cameraCenter?: { lat: number, lng: number } | null, selectedEntity?: { type: string, id: string | number, extra?: any } | null }) {
|
||||
export default function RadioInterceptPanel({ data, isEavesdropping, setIsEavesdropping, eavesdropLocation, cameraCenter, selectedEntity }: { data: DashboardData, isEavesdropping?: boolean, setIsEavesdropping?: (val: boolean) => void, eavesdropLocation?: { lat: number, lng: number } | null, cameraCenter?: { lat: number, lng: number } | null, selectedEntity?: SelectedEntity | null }) {
|
||||
const [isMinimized, setIsMinimized] = useState(true);
|
||||
const [feeds, setFeeds] = useState<any[]>([]);
|
||||
const [activeFeed, setActiveFeed] = useState<any | null>(null);
|
||||
const [feeds, setFeeds] = useState<RadioFeed[]>([]);
|
||||
const [activeFeed, setActiveFeed] = useState<RadioFeed | null>(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
@@ -113,7 +114,7 @@ export default function RadioInterceptPanel({ data, isEavesdropping, setIsEavesd
|
||||
}
|
||||
}, [eavesdropLocation]);
|
||||
|
||||
const playFeed = (feed: any) => {
|
||||
const playFeed = (feed: RadioFeed) => {
|
||||
if (isScanning && scanTimeoutRef.current) {
|
||||
clearTimeout(scanTimeoutRef.current);
|
||||
setIsScanning(false);
|
||||
@@ -135,10 +136,10 @@ export default function RadioInterceptPanel({ data, isEavesdropping, setIsEavesd
|
||||
useEffect(() => {
|
||||
if (activeFeed && isPlaying) {
|
||||
if (!audioRef.current) {
|
||||
const audio = new Audio(activeFeed.stream_url);
|
||||
const audio = new Audio(activeFeed.stream_url || '');
|
||||
audioRef.current = audio;
|
||||
} else {
|
||||
audioRef.current.src = activeFeed.stream_url;
|
||||
audioRef.current.src = activeFeed.stream_url || '';
|
||||
}
|
||||
audioRef.current.volume = volume;
|
||||
audioRef.current.play().catch(e => console.log("Audio play blocked", e));
|
||||
@@ -382,7 +383,7 @@ export default function RadioInterceptPanel({ data, isEavesdropping, setIsEavesd
|
||||
{feeds.length === 0 ? (
|
||||
<div className="text-[10px] text-cyan-700 font-mono text-center p-4">SEARCHING FREQUENCIES...</div>
|
||||
) : (
|
||||
feeds.map((feed: any, idx: number) => (
|
||||
feeds.map((feed: RadioFeed, idx: number) => (
|
||||
<div
|
||||
key={feed.id}
|
||||
onClick={() => playFeed(feed)}
|
||||
|
||||
@@ -32,6 +32,7 @@ const FRESHNESS_MAP: Record<string, string> = {
|
||||
ships_cargo: "ships",
|
||||
ships_civilian: "ships",
|
||||
ships_passenger: "ships",
|
||||
ships_tracked_yachts: "ships",
|
||||
ukraine_frontline: "frontlines",
|
||||
global_incidents: "gdelt",
|
||||
cctv: "cctv",
|
||||
@@ -59,8 +60,9 @@ const POTUS_ICAOS: Record<string, { label: string; type: string }> = {
|
||||
'AE5E77': { label: 'Marine One (VH-92A)', type: 'M1' },
|
||||
'AE5E79': { label: 'Marine One (VH-92A)', type: 'M1' },
|
||||
};
|
||||
import type { DashboardData, ActiveLayers, SelectedEntity } from "@/types/dashboard";
|
||||
|
||||
const WorldviewLeftPanel = React.memo(function WorldviewLeftPanel({ data, activeLayers, setActiveLayers, onSettingsClick, onLegendClick, gibsDate, setGibsDate, gibsOpacity, setGibsOpacity, onEntityClick, onFlyTo }: { data: any; activeLayers: any; setActiveLayers: any; onSettingsClick?: () => void; onLegendClick?: () => void; gibsDate?: string; setGibsDate?: (d: string) => void; gibsOpacity?: number; setGibsOpacity?: (o: number) => void; onEntityClick?: (entity: { type: string; id: number; extra?: any }) => void; onFlyTo?: (lat: number, lng: number) => void }) {
|
||||
const WorldviewLeftPanel = React.memo(function WorldviewLeftPanel({ data, activeLayers, setActiveLayers, onSettingsClick, onLegendClick, gibsDate, setGibsDate, gibsOpacity, setGibsOpacity, onEntityClick, onFlyTo }: { data: DashboardData; activeLayers: ActiveLayers; setActiveLayers: React.Dispatch<React.SetStateAction<ActiveLayers>>; onSettingsClick?: () => void; onLegendClick?: () => void; gibsDate?: string; setGibsDate?: (d: string) => void; gibsOpacity?: number; setGibsOpacity?: (o: number) => void; onEntityClick?: (entity: SelectedEntity) => void; onFlyTo?: (lat: number, lng: number) => void }) {
|
||||
const [isMinimized, setIsMinimized] = useState(false);
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const [gibsPlaying, setGibsPlaying] = useState(false);
|
||||
@@ -92,18 +94,19 @@ const WorldviewLeftPanel = React.memo(function WorldviewLeftPanel({ data, active
|
||||
}, [gibsPlaying, gibsDate, setGibsDate]);
|
||||
|
||||
// Compute ship category counts (memoized — ships array can be 1000+ items)
|
||||
const { militaryShipCount, cargoShipCount, passengerShipCount, civilianShipCount } = useMemo(() => {
|
||||
const { militaryShipCount, cargoShipCount, passengerShipCount, civilianShipCount, trackedYachtCount } = useMemo(() => {
|
||||
const ships = data?.ships;
|
||||
if (!ships || !ships.length) return { militaryShipCount: 0, cargoShipCount: 0, passengerShipCount: 0, civilianShipCount: 0 };
|
||||
let military = 0, cargo = 0, passenger = 0, civilian = 0;
|
||||
if (!ships || !ships.length) return { militaryShipCount: 0, cargoShipCount: 0, passengerShipCount: 0, civilianShipCount: 0, trackedYachtCount: 0 };
|
||||
let military = 0, cargo = 0, passenger = 0, civilian = 0, trackedYacht = 0;
|
||||
for (const s of ships) {
|
||||
if (s.yacht_alert) { trackedYacht++; continue; }
|
||||
const t = s.type;
|
||||
if (t === 'carrier' || t === 'military_vessel') military++;
|
||||
else if (t === 'tanker' || t === 'cargo') cargo++;
|
||||
else if (t === 'passenger') passenger++;
|
||||
else civilian++;
|
||||
}
|
||||
return { militaryShipCount: military, cargoShipCount: cargo, passengerShipCount: passenger, civilianShipCount: civilian };
|
||||
return { militaryShipCount: military, cargoShipCount: cargo, passengerShipCount: passenger, civilianShipCount: civilian, trackedYachtCount: trackedYacht };
|
||||
}, [data?.ships]);
|
||||
|
||||
// Find POTUS fleet planes currently airborne from tracked flights
|
||||
@@ -133,6 +136,7 @@ const WorldviewLeftPanel = React.memo(function WorldviewLeftPanel({ data, active
|
||||
{ id: "ships_cargo", name: "Cargo / Tankers", source: "AIS Stream", count: cargoShipCount, icon: Ship },
|
||||
{ id: "ships_civilian", name: "Civilian Vessels", source: "AIS Stream", count: civilianShipCount, icon: Anchor },
|
||||
{ id: "ships_passenger", name: "Cruise / Passenger", source: "AIS Stream", count: passengerShipCount, icon: Anchor },
|
||||
{ id: "ships_tracked_yachts", name: "Tracked Yachts", source: "Yacht-Alert DB", count: trackedYachtCount, icon: Eye },
|
||||
{ id: "ukraine_frontline", name: "Ukraine Frontline", source: "DeepStateMap", count: data?.frontlines ? 1 : 0, icon: AlertTriangle },
|
||||
{ id: "global_incidents", name: "Global Incidents", source: "GDELT", count: data?.gdelt?.length || 0, icon: Activity },
|
||||
{ id: "cctv", name: "CCTV Mesh", source: "CCTV Mesh + Street View", count: data?.cctv?.length || 0, icon: Cctv },
|
||||
@@ -314,8 +318,8 @@ const WorldviewLeftPanel = React.memo(function WorldviewLeftPanel({ data, active
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{active && layer.count > 0 && (
|
||||
<span className="text-[10px] text-gray-300 font-mono">{layer.count.toLocaleString()}</span>
|
||||
{active && (layer.count ?? 0) > 0 && (
|
||||
<span className="text-[10px] text-gray-300 font-mono">{(layer.count ?? 0).toLocaleString()}</span>
|
||||
)}
|
||||
<div className={`text-[9px] font-mono tracking-wider px-2 py-0.5 rounded-full border ${active
|
||||
? 'border-cyan-500/50 text-cyan-400 bg-cyan-950/30 shadow-[0_0_10px_rgba(34,211,238,0.2)]'
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import type { MapEffects } from "@/types/dashboard";
|
||||
|
||||
const WorldviewRightPanel = React.memo(function WorldviewRightPanel({ effects, setEffects, setUiVisible }: { effects: any; setEffects: any; setUiVisible: any }) {
|
||||
const WorldviewRightPanel = React.memo(function WorldviewRightPanel({ effects, setEffects, setUiVisible }: { effects: MapEffects; setEffects: (e: MapEffects) => void; setUiVisible: (v: boolean) => void }) {
|
||||
const [isMinimized, setIsMinimized] = useState(true);
|
||||
const [currentTime, setCurrentTime] = useState({ date: "XXXX-XX-XX", time: "00:00:00" });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user