diff --git a/backend/__pycache__/debug_log.cpython-314.pyc b/backend/__pycache__/debug_log.cpython-314.pyc new file mode 100644 index 0000000..3880fcc Binary files /dev/null and b/backend/__pycache__/debug_log.cpython-314.pyc differ diff --git a/backend/api/__pycache__/__init__.cpython-314.pyc b/backend/api/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..dd90041 Binary files /dev/null and b/backend/api/__pycache__/__init__.cpython-314.pyc differ diff --git a/backend/api/__pycache__/router.cpython-314.pyc b/backend/api/__pycache__/router.cpython-314.pyc new file mode 100644 index 0000000..b560ff5 Binary files /dev/null and b/backend/api/__pycache__/router.cpython-314.pyc differ diff --git a/backend/api/router.py b/backend/api/router.py index c6f2b70..294f5fe 100644 --- a/backend/api/router.py +++ b/backend/api/router.py @@ -1,5 +1,5 @@ from fastapi import APIRouter -from api.v1.endpoints import auth, satellites, collisions, agents, dashboard, catalog, weather +from api.v1.endpoints import auth, satellites, collisions, agents, dashboard, catalog, weather, events api_router = APIRouter() @@ -10,6 +10,7 @@ api_router.include_router(dashboard.router, prefix="/dashboard", tags=["Dashboard"]) api_router.include_router(catalog.router, prefix="/catalog", tags=["Orbital Catalog"]) api_router.include_router(weather.router, prefix="/weather", tags=["Space Weather"]) +api_router.include_router(events.router, prefix="/events", tags=["Mission Events"]) @api_router.get("/health", tags=["Health"]) diff --git a/backend/api/v1/__pycache__/__init__.cpython-314.pyc b/backend/api/v1/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..5b90c77 Binary files /dev/null and b/backend/api/v1/__pycache__/__init__.cpython-314.pyc differ diff --git a/backend/api/v1/endpoints/__pycache__/__init__.cpython-314.pyc b/backend/api/v1/endpoints/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..ce28fe4 Binary files /dev/null and b/backend/api/v1/endpoints/__pycache__/__init__.cpython-314.pyc differ diff --git a/backend/api/v1/endpoints/__pycache__/auth.cpython-314.pyc b/backend/api/v1/endpoints/__pycache__/auth.cpython-314.pyc new file mode 100644 index 0000000..10dfd48 Binary files /dev/null and b/backend/api/v1/endpoints/__pycache__/auth.cpython-314.pyc differ diff --git a/backend/api/v1/endpoints/events.py b/backend/api/v1/endpoints/events.py new file mode 100644 index 0000000..40eae19 --- /dev/null +++ b/backend/api/v1/endpoints/events.py @@ -0,0 +1,157 @@ +""" +FastAPI Endpoint — Mission Control Live Event Stream API +========================================================== +Returns historical and live mission events (conjunctions, launches, +maneuvers, debris fragmentation, space weather alerts, system diagnostics). +""" + +from typing import List, Optional +from datetime import datetime, timezone, timedelta +from fastapi import APIRouter, Query, Depends +from sqlalchemy.orm import Session + +from database.session import get_db +from models.db_models import OrbitalEvent, CollisionPrediction, SpaceWeather + +router = APIRouter() + +# Seed mock events generator for instant deployment / fallback +STATIC_EVENTS = [ + { + "id": "evt_be_101", + "timestamp": (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat(), + "category": "CONJUNCTION", + "severity": "CRITICAL", + "title": "CRITICAL CONJUNCTION RISK: ISS (ZARYA) vs DEBRIS 2021-055A", + "description": "Close-approach predicted within key warning sphere. Collision probability exceeds emergency response threshold.", + "satellite_name": "ISS (ZARYA)", + "norad_id": "25544", + "cospar_id": "1998-067A", + "is_high_priority": True, + "acknowledged": False, + "telemetry": { + "miss_distance_m": 142.5, + "collision_probability": 0.0342, + "relative_velocity_kms": 14.2, + "orbit_altitude_km": 418.6 + }, + "external_references": [ + {"label": "Space-Track Conjunction Data", "url": "https://www.space-track.org"}, + {"label": "CelesTrak CDM", "url": "https://celestrak.org"} + ] + }, + { + "id": "evt_be_102", + "timestamp": (datetime.now(timezone.utc) - timedelta(minutes=18)).isoformat(), + "category": "SPACE_WEATHER", + "severity": "HIGH", + "title": "X1.2-CLASS SOLAR FLARE & GEOMAGNETIC STORM WARNING", + "description": "Active region AR3664 produced an X1.2 solar flare with an associated Earth-directed Coronal Mass Ejection (CME).", + "satellite_name": "GLOBAL WEATHER MONITOR", + "norad_id": "43012", + "is_high_priority": True, + "acknowledged": False, + "telemetry": { + "kp_index": 7.3, + "solar_flux_sfu": 245.8 + }, + "external_references": [ + {"label": "NOAA Space Weather Prediction Center", "url": "https://www.swpc.noaa.gov"} + ] + }, + { + "id": "evt_be_103", + "timestamp": (datetime.now(timezone.utc) - timedelta(minutes=45)).isoformat(), + "category": "MANEUVER", + "severity": "MEDIUM", + "title": "COLLISION AVOIDANCE MANEUVER EXECUTED — SENTINEL-6A", + "description": "Thrust duration 14.2s completed successfully. Perigee raised by +420m to clear debris field path.", + "satellite_name": "SENTINEL-6A", + "norad_id": "46984", + "cospar_id": "2020-086A", + "is_high_priority": False, + "acknowledged": True, + "telemetry": { + "delta_v_ms": 0.42, + "fuel_cost_kg": 1.84, + "orbit_altitude_km": 1336.2, + "velocity_kms": 7.21 + } + }, + { + "id": "evt_be_104", + "timestamp": (datetime.now(timezone.utc) - timedelta(minutes=85)).isoformat(), + "category": "LAUNCH", + "severity": "LOW", + "title": "ORBITAL INSERTION CONFIRMED — STARLINK-G8-12 BATCH", + "description": "Falcon 9 second stage deployment nominal. 23 spacecraft inserted into 290 km initial checkout orbit.", + "satellite_name": "STARLINK-G8-12", + "norad_id": "59102", + "cospar_id": "2026-014A", + "is_high_priority": False, + "acknowledged": True, + "telemetry": { + "orbit_altitude_km": 290.4, + "inclination_deg": 53.2, + "velocity_kms": 7.73 + } + }, + { + "id": "evt_be_105", + "timestamp": (datetime.now(timezone.utc) - timedelta(minutes=130)).isoformat(), + "category": "DEBRIS", + "severity": "HIGH", + "title": "NEW DEBRIS FRAGMENTATION EVENT DETECTED", + "description": "Breakup alert in LEO orbit (720 km). 48 new trackable object vectors registered by Ground Radar Network.", + "satellite_name": "COSMOS-1408 FRAGMENT CLUSTER", + "norad_id": "49812", + "is_high_priority": True, + "acknowledged": False, + "telemetry": { + "fragment_count": 48, + "orbit_altitude_km": 720.5 + } + } +] + +@router.get("", response_model=dict) +def get_mission_events( + category: Optional[str] = Query(None, description="Category filter (LAUNCH, CONJUNCTION, MANEUVER, DEBRIS, SPACE_WEATHER, SYSTEM)"), + severity: Optional[str] = Query(None, description="Severity filter (LOW, MEDIUM, HIGH, CRITICAL)"), + search: Optional[str] = Query(None, description="Search query across titles and descriptions"), + limit: int = Query(50, ge=1, le=200), + db: Session = Depends(get_db) +): + """ + Retrieve live mission control events stream with multi-field filtering. + """ + results = list(STATIC_EVENTS) + + # Category filter + if category and category.upper() != "ALL": + results = [e for e in results if e["category"].upper() == category.upper()] + + # Severity filter + if severity and severity.upper() != "ALL": + results = [e for e in results if e["severity"].upper() == severity.upper()] + + # Search filter + if search and search.strip(): + q = search.strip().lower() + results = [ + e for e in results + if q in e["title"].lower() or q in e["description"].lower() or q in (e.get("satellite_name") or "").lower() + ] + + # Limit + results = results[:limit] + + return { + "success": True, + "message": "Mission control events retrieved successfully", + "data": results, + "metadata": { + "total": len(results), + "source": "Kepler Strategic Command Event Engine" + } + } diff --git a/backend/app/__pycache__/__init__.cpython-314.pyc b/backend/app/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..7b31f9f Binary files /dev/null and b/backend/app/__pycache__/__init__.cpython-314.pyc differ diff --git a/backend/app/__pycache__/main.cpython-314.pyc b/backend/app/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000..9bf6dd6 Binary files /dev/null and b/backend/app/__pycache__/main.cpython-314.pyc differ diff --git a/backend/app/core/__pycache__/__init__.cpython-314.pyc b/backend/app/core/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..5e85155 Binary files /dev/null and b/backend/app/core/__pycache__/__init__.cpython-314.pyc differ diff --git a/backend/app/core/__pycache__/config.cpython-314.pyc b/backend/app/core/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000..432ef3a Binary files /dev/null and b/backend/app/core/__pycache__/config.cpython-314.pyc differ diff --git a/backend/app/core/__pycache__/error_handlers.cpython-314.pyc b/backend/app/core/__pycache__/error_handlers.cpython-314.pyc new file mode 100644 index 0000000..3391163 Binary files /dev/null and b/backend/app/core/__pycache__/error_handlers.cpython-314.pyc differ diff --git a/backend/app/core/__pycache__/exceptions.cpython-314.pyc b/backend/app/core/__pycache__/exceptions.cpython-314.pyc new file mode 100644 index 0000000..0a419c2 Binary files /dev/null and b/backend/app/core/__pycache__/exceptions.cpython-314.pyc differ diff --git a/backend/schemas/__pycache__/__init__.cpython-314.pyc b/backend/schemas/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..9450626 Binary files /dev/null and b/backend/schemas/__pycache__/__init__.cpython-314.pyc differ diff --git a/backend/schemas/__pycache__/api_schemas.cpython-314.pyc b/backend/schemas/__pycache__/api_schemas.cpython-314.pyc new file mode 100644 index 0000000..9c634b8 Binary files /dev/null and b/backend/schemas/__pycache__/api_schemas.cpython-314.pyc differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 38fa1f8..e65904a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -19,6 +19,7 @@ import { Debris } from '@/pages/Debris'; import { CollisionCenter } from '@/pages/CollisionCenter'; import { AIAgents } from '@/pages/AIAgents'; import { MissionPlanner } from '@/pages/MissionPlanner'; +import EventTimelinePage from '@/pages/EventTimeline'; import { Settings } from '@/pages/Settings'; import { Toaster } from 'sonner'; import { toastOptions } from './constants/toast'; @@ -89,6 +90,7 @@ function App() { }> } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/layouts/MainLayout.tsx b/frontend/src/components/layouts/MainLayout.tsx index 4889c3d..4a51ecf 100644 --- a/frontend/src/components/layouts/MainLayout.tsx +++ b/frontend/src/components/layouts/MainLayout.tsx @@ -45,6 +45,7 @@ export const MainLayout: React.FC = () => { const navItems = [ { name: 'Dashboard', path: '/dashboard', icon: 'dashboard' }, + { name: 'Event Timeline', path: '/dashboard/timeline', icon: 'timeline' }, { name: 'Space Traffic', path: '/dashboard/space-traffic', icon: 'language' }, { name: 'Space Weather', path: '/dashboard/space-weather', icon: 'wb_sunny' }, { name: 'Satellites', path: '/dashboard/satellites', icon: 'satellite_alt' }, diff --git a/frontend/src/components/timeline/EventTimelineCard.tsx b/frontend/src/components/timeline/EventTimelineCard.tsx new file mode 100644 index 0000000..58167ed --- /dev/null +++ b/frontend/src/components/timeline/EventTimelineCard.tsx @@ -0,0 +1,356 @@ +import React, { useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useNavigate } from 'react-router-dom'; +import { MaterialIcon } from '@/components/MaterialIcon'; +import type { TimelineEvent, EventSeverity, TimelineEventCategory } from '@/types/events'; +import { logEvent } from '@/store/logbookStore'; + +interface EventTimelineCardProps { + event: TimelineEvent; + onAcknowledge: (id: string) => void; +} + +const severityConfig: Record< + EventSeverity, + { border: string; bg: string; text: string; glow: string; icon: string } +> = { + CRITICAL: { + border: 'border-status-emergency/70', + bg: 'bg-status-emergency/10', + text: 'text-status-emergency', + glow: 'shadow-[0_0_15px_rgba(255,59,48,0.2)]', + icon: 'error', + }, + HIGH: { + border: 'border-status-warning/70', + bg: 'bg-status-warning/10', + text: 'text-status-warning', + glow: 'shadow-[0_0_12px_rgba(255,149,0,0.15)]', + icon: 'warning', + }, + MEDIUM: { + border: 'border-amber-400/50', + bg: 'bg-amber-500/10', + text: 'text-amber-400', + glow: 'shadow-[0_0_10px_rgba(245,158,11,0.1)]', + icon: 'info', + }, + LOW: { + border: 'border-primary-container/40', + bg: 'bg-primary-container/10', + text: 'text-primary-container', + glow: '', + icon: 'check_circle', + }, +}; + +const categoryConfig: Record< + TimelineEventCategory, + { label: string; icon: string; color: string } +> = { + LAUNCH: { label: 'LAUNCH', icon: 'rocket_launch', color: 'text-cyan-400' }, + CONJUNCTION: { label: 'CONJUNCTION', icon: 'warning', color: 'text-status-emergency' }, + MANEUVER: { label: 'MANEUVER', icon: 'orbit', color: 'text-emerald-400' }, + DEBRIS: { label: 'DEBRIS', icon: 'delete_sweep', color: 'text-purple-400' }, + SPACE_WEATHER: { label: 'SPACE WEATHER', icon: 'wb_sunny', color: 'text-amber-400' }, + SYSTEM: { label: 'SYSTEM', icon: 'settings', color: 'text-blue-400' }, +}; + +function formatUtc(timestamp: string): string { + const d = new Date(timestamp); + return d.toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; +} + +function formatRelativeTime(timestamp: string): string { + const diffSec = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1000); + if (diffSec < 60) return `${diffSec}s AGO`; + const min = Math.floor(diffSec / 60); + if (min < 60) return `${min}m AGO`; + const hr = Math.floor(min / 60); + if (hr < 24) return `${hr}h AGO`; + const days = Math.floor(hr / 24); + return `${days}d AGO`; +} + +export const EventTimelineCard: React.FC = ({ event, onAcknowledge }) => { + const [expanded, setExpanded] = useState(false); + const navigate = useNavigate(); + + const sev = severityConfig[event.severity]; + const cat = categoryConfig[event.category]; + + const handleManualLog = (e: React.MouseEvent) => { + e.stopPropagation(); + logEvent('MISSION', event.severity, event.title, event.description, { + NORAD_ID: event.norad_id || 'N/A', + CATEGORY: event.category, + SOURCE: 'User Timeline Card', + }); + }; + + return ( + + {/* Left severity color bar */} +
+ + {/* Main Collapsed Header Content */} +
setExpanded(!expanded)} + className="p-4 md:p-5 pl-5 md:pl-6 cursor-pointer flex flex-col md:flex-row md:items-center justify-between gap-4 select-none" + > +
+ {/* Category Icon */} +
+ +
+ +
+
+ {/* Category Badge */} + + {cat.label} + + + {/* Severity Badge */} + + {event.severity} + + + {/* Satellite / NORAD Badge */} + {event.satellite_name && ( + + {event.satellite_name}{' '} + {event.norad_id ? `(#${event.norad_id})` : ''} + + )} + + {/* Timestamp */} + + {formatUtc(event.timestamp)} + + + + {formatRelativeTime(event.timestamp)} + +
+ +

+ {event.title} +

+

+ {event.description} +

+
+
+ + {/* Right Status Badges & Chevron toggle */} +
+ {event.acknowledged ? ( + + + ACKNOWLEDGED + + ) : ( + + )} + +
+ +
+
+
+ + {/* Expanded Details Panel */} + + {expanded && ( + + {/* Full description */} +
+

+ EVENT SUMMARY & ANALYTICS +

+

+ {event.description} +

+
+ + {/* Telemetry Metrics Grid */} + {event.telemetry && Object.keys(event.telemetry).length > 0 && ( +
+

+ TELEMETRY PARAMETERS +

+
+ {event.telemetry.miss_distance_m !== undefined && ( +
+

MISS DISTANCE

+

+ {event.telemetry.miss_distance_m < 1000 + ? `${event.telemetry.miss_distance_m} m` + : `${(event.telemetry.miss_distance_m / 1000).toFixed(2)} km`} +

+
+ )} + + {event.telemetry.collision_probability !== undefined && ( +
+

PROBABILITY

+

+ {(event.telemetry.collision_probability * 100).toFixed(2)}% +

+
+ )} + + {event.telemetry.orbit_altitude_km !== undefined && ( +
+

ALTITUDE

+

+ {event.telemetry.orbit_altitude_km} km +

+
+ )} + + {event.telemetry.velocity_kms !== undefined && ( +
+

VELOCITY

+

+ {event.telemetry.velocity_kms} km/s +

+
+ )} + + {event.telemetry.delta_v_ms !== undefined && ( +
+

DELTA-V BURNT

+

+ {event.telemetry.delta_v_ms} m/s +

+
+ )} + + {event.telemetry.kp_index !== undefined && ( +
+

KP INDEX

+

+ {event.telemetry.kp_index} +

+
+ )} + + {event.telemetry.fragment_count !== undefined && ( +
+

TRACKED FRAGMENTS

+

+ {event.telemetry.fragment_count} +

+
+ )} +
+
+ )} + + {/* External References links */} + {event.external_references && event.external_references.length > 0 && ( +
+

+ EXTERNAL TELEMETRY SOURCES +

+
+ {event.external_references.map((ref, i) => ( + + + {ref.label} + + ))} +
+
+ )} + + {/* Action Bar */} +
+ + + {event.category === 'CONJUNCTION' && ( + + )} + + {event.category === 'SPACE_WEATHER' && ( + + )} + + +
+
+ )} +
+ + ); +}; diff --git a/frontend/src/components/timeline/EventTimelineFilterBar.tsx b/frontend/src/components/timeline/EventTimelineFilterBar.tsx new file mode 100644 index 0000000..0dae72f --- /dev/null +++ b/frontend/src/components/timeline/EventTimelineFilterBar.tsx @@ -0,0 +1,202 @@ +import React from 'react'; +import { MaterialIcon } from '@/components/MaterialIcon'; +import type { + EventFilterParams, + TimelineEventCategory, + EventSeverity, + TimeRangeFilter, +} from '@/types/events'; + +interface EventTimelineFilterBarProps { + filters: EventFilterParams; + streamStatus: 'LIVE' | 'PAUSED' | 'DISCONNECTED'; + onFilterChange: (key: K, value: EventFilterParams[K]) => void; + onResetFilters: () => void; + onTogglePauseStream: () => void; + onSimulateIncident: () => void; + filteredCount: number; + totalCount: number; +} + +const CATEGORIES: Array<{ label: string; value: TimelineEventCategory | 'ALL'; icon: string }> = [ + { label: 'ALL EVENTS', value: 'ALL', icon: 'apps' }, + { label: 'LAUNCHES', value: 'LAUNCH', icon: 'rocket_launch' }, + { label: 'CONJUNCTIONS', value: 'CONJUNCTION', icon: 'warning' }, + { label: 'MANEUVERS', value: 'MANEUVER', icon: 'orbit' }, + { label: 'DEBRIS', value: 'DEBRIS', icon: 'delete_sweep' }, + { label: 'SPACE WEATHER', value: 'SPACE_WEATHER', icon: 'wb_sunny' }, + { label: 'SYSTEM', value: 'SYSTEM', icon: 'settings' }, +]; + +const SEVERITIES: Array<{ label: string; value: EventSeverity | 'ALL'; color: string }> = [ + { label: 'ALL SEVERITIES', value: 'ALL', color: 'text-on-surface-variant' }, + { label: 'CRITICAL', value: 'CRITICAL', color: 'text-status-emergency' }, + { label: 'HIGH', value: 'HIGH', color: 'text-status-warning' }, + { label: 'MEDIUM', value: 'MEDIUM', color: 'text-amber-400' }, + { label: 'LOW', value: 'LOW', color: 'text-primary-container' }, +]; + +const TIME_RANGES: Array<{ label: string; value: TimeRangeFilter }> = [ + { label: 'ALL TIME', value: 'ALL' }, + { label: 'LAST 1 HOUR', value: '1H' }, + { label: 'LAST 24 HOURS', value: '24H' }, + { label: 'LAST 7 DAYS', value: '7D' }, +]; + +export const EventTimelineFilterBar: React.FC = ({ + filters, + streamStatus, + onFilterChange, + onResetFilters, + onTogglePauseStream, + onSimulateIncident, + filteredCount, + totalCount, +}) => { + const isLive = streamStatus === 'LIVE'; + + return ( +
+ {/* Top Controller Row: Stream Status & Global Action Bar */} +
+ {/* Stream Status indicator */} +
+ + + + SHOWING {filteredCount} OF{' '} + {totalCount} EVENTS + +
+ + {/* Action Controls */} +
+ + + + + {/* Sort order toggle */} + +
+
+ + {/* Category Pills Row */} +
+ {CATEGORIES.map((cat) => { + const isActive = filters.category === cat.value; + return ( + + ); + })} +
+ + {/* Search Input & Secondary Filters Grid */} +
+ {/* Real-time Search Input */} +
+ + onFilterChange('searchQuery', e.target.value)} + placeholder="SEARCH TITLE, NORAD ID, SATELLITE..." + className="w-full bg-surface-container-low border border-border-panel/80 rounded px-3 py-2 pl-9 text-xs font-technical-data text-on-surface placeholder:text-primary/30 focus:outline-none focus:border-primary-container transition-ui" + /> + {filters.searchQuery && ( + + )} +
+ + {/* Severity Selector Dropdown */} +
+ +
+ + {/* Time Range Selector Dropdown */} +
+ +
+
+
+ ); +}; diff --git a/frontend/src/components/timeline/HighPriorityIncidentBanner.tsx b/frontend/src/components/timeline/HighPriorityIncidentBanner.tsx new file mode 100644 index 0000000..d69ec3d --- /dev/null +++ b/frontend/src/components/timeline/HighPriorityIncidentBanner.tsx @@ -0,0 +1,154 @@ +import React from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useNavigate } from 'react-router-dom'; +import { MaterialIcon } from '@/components/MaterialIcon'; +import type { TimelineEvent } from '@/types/events'; + +interface HighPriorityIncidentBannerProps { + incidents: TimelineEvent[]; + onAcknowledge: (id: string) => void; +} + +export const HighPriorityIncidentBanner: React.FC = ({ + incidents, + onAcknowledge, +}) => { + const navigate = useNavigate(); + + if (incidents.length === 0) return null; + + const currentIncident = incidents[0]; // Show top urgent incident + const isCritical = currentIncident.severity === 'CRITICAL'; + + return ( + + + {/* Background CRT pulse scan line effect */} +
+ +
+
+ {/* Pulsing hazard icon badge */} +
+ +
+ +
+
+ + HIGH PRIORITY {currentIncident.severity} INCIDENT + + + {incidents.length > 1 && ( + + +{incidents.length - 1} MORE ACTIVE ALERTS + + )} + + + {new Date(currentIncident.timestamp).toUTCString().replace('GMT', 'UTC')} + +
+ +

+ {currentIncident.title} +

+

+ {currentIncident.description} +

+ + {/* Telemetry highlights */} + {currentIncident.telemetry && ( +
+ {currentIncident.telemetry.miss_distance_m !== undefined && ( + + MISS DISTANCE:{' '} + + {currentIncident.telemetry.miss_distance_m < 1000 + ? `${currentIncident.telemetry.miss_distance_m}m` + : `${(currentIncident.telemetry.miss_distance_m / 1000).toFixed(2)}km`} + + + )} + {currentIncident.telemetry.collision_probability !== undefined && ( + + PROBABILITY:{' '} + + {(currentIncident.telemetry.collision_probability * 100).toFixed(2)}% + + + )} + {currentIncident.telemetry.kp_index !== undefined && ( + + KP INDEX: {currentIncident.telemetry.kp_index} + + )} +
+ )} +
+
+ + {/* Direct Action Buttons */} +
+ {currentIncident.category === 'CONJUNCTION' && ( + + )} + + {currentIncident.category === 'CONJUNCTION' && ( + + )} + + +
+
+ + + ); +}; diff --git a/frontend/src/hooks/useEventTimeline.ts b/frontend/src/hooks/useEventTimeline.ts new file mode 100644 index 0000000..a5c02f1 --- /dev/null +++ b/frontend/src/hooks/useEventTimeline.ts @@ -0,0 +1,149 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useEventTimelineStore } from '@/store/eventTimelineStore'; + +export function useEventTimeline() { + const events = useEventTimelineStore((s) => s.events); + const filters = useEventTimelineStore((s) => s.filters); + const streamStatus = useEventTimelineStore((s) => s.streamStatus); + const streamSpeed = useEventTimelineStore((s) => s.streamSpeed); + const selectedEventId = useEventTimelineStore((s) => s.selectedEventId); + + const addEvent = useEventTimelineStore((s) => s.addEvent); + const setFilter = useEventTimelineStore((s) => s.setFilter); + const resetFilters = useEventTimelineStore((s) => s.resetFilters); + const togglePauseStream = useEventTimelineStore((s) => s.togglePauseStream); + const setStreamSpeed = useEventTimelineStore((s) => s.setStreamSpeed); + const setSelectedEventId = useEventTimelineStore((s) => s.setSelectedEventId); + const acknowledgeEvent = useEventTimelineStore((s) => s.acknowledgeEvent); + const simulateIncident = useEventTimelineStore((s) => s.simulateIncident); + const clearAllEvents = useEventTimelineStore((s) => s.clearAllEvents); + const startLiveStreaming = useEventTimelineStore((s) => s.startLiveStreaming); + + const [currentTime, setCurrentTime] = useState(() => Date.now()); + + useEffect(() => { + const timer = setInterval(() => setCurrentTime(Date.now()), 10000); + return () => clearInterval(timer); + }, []); + + // Automatically start real-time event generator when hook is mounted + useEffect(() => { + const cleanup = startLiveStreaming(); + return () => cleanup(); + }, [startLiveStreaming]); + + // Compute filtered & sorted events + const filteredEvents = useMemo(() => { + return events.filter((evt) => { + // Category filter + if (filters.category !== 'ALL' && evt.category !== filters.category) { + return false; + } + + // Severity filter + if (filters.severity !== 'ALL' && evt.severity !== filters.severity) { + return false; + } + + // High priority filter + if (filters.highPriorityOnly && !evt.is_high_priority) { + return false; + } + + // NORAD ID filter + if (filters.noradId && evt.norad_id !== filters.noradId) { + return false; + } + + // Time Range filter + if (filters.timeRange !== 'ALL') { + const evtTime = new Date(evt.timestamp).getTime(); + const diffMs = currentTime - evtTime; + if (filters.timeRange === '1H' && diffMs > 60 * 60 * 1000) return false; + if (filters.timeRange === '24H' && diffMs > 24 * 60 * 60 * 1000) return false; + if (filters.timeRange === '7D' && diffMs > 7 * 24 * 60 * 60 * 1000) return false; + } + + // Search Query filter (matches title, description, satellite name, NORAD ID, category) + if (filters.searchQuery.trim()) { + const q = filters.searchQuery.toLowerCase().trim(); + const textToSearch = [ + evt.title, + evt.description, + evt.satellite_name, + evt.norad_id, + evt.cospar_id, + evt.category, + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + + if (!textToSearch.includes(q)) return false; + } + + return true; + }).sort((a, b) => { + const tA = new Date(a.timestamp).getTime(); + const tB = new Date(b.timestamp).getTime(); + return filters.sortOrder === 'NEWEST_FIRST' ? tB - tA : tA - tB; + }); + }, [events, filters, currentTime]); + + // Compute stats + const stats = useMemo(() => { + const total = events.length; + const unacknowledged = events.filter((e) => !e.acknowledged).length; + const critical = events.filter((e) => e.severity === 'CRITICAL' && !e.acknowledged).length; + const high = events.filter((e) => e.severity === 'HIGH' && !e.acknowledged).length; + + const byCategory: Record = { + LAUNCH: 0, + CONJUNCTION: 0, + MANEUVER: 0, + DEBRIS: 0, + SPACE_WEATHER: 0, + SYSTEM: 0, + }; + + events.forEach((evt) => { + if (byCategory[evt.category] !== undefined) { + byCategory[evt.category] += 1; + } + }); + + return { + total, + unacknowledged, + critical, + high, + byCategory, + }; + }, [events]); + + const unacknowledgedCriticalEvents = useMemo(() => { + return events.filter((evt) => (evt.severity === 'CRITICAL' || evt.severity === 'HIGH') && !evt.acknowledged); + }, [events]); + + return { + events: filteredEvents, + rawEventsCount: events.length, + filters, + streamStatus, + streamSpeed, + selectedEventId, + stats, + unacknowledgedCriticalEvents, + + // Actions + addEvent, + setFilter, + resetFilters, + togglePauseStream, + setStreamSpeed, + setSelectedEventId, + acknowledgeEvent, + simulateIncident, + clearAllEvents, + }; +} diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 329c92a..4b9af3e 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,4 +1,5 @@ import React, { useRef, useEffect, useMemo } from 'react'; +import { Link } from 'react-router-dom'; import { motion } from 'framer-motion'; import { EarthTwin, type EarthTwinHandle } from '@/components/EarthTwin'; import { MaterialIcon } from '@/components/MaterialIcon'; @@ -215,7 +216,13 @@ export const Dashboard: React.FC = () => { LIVE COLLISION TIMELINE -
+
+ + FULL EVENT STREAM + {collisions.isFetching && ( UPDATING\u2026 )} diff --git a/frontend/src/pages/EventTimeline.tsx b/frontend/src/pages/EventTimeline.tsx new file mode 100644 index 0000000..b8f43be --- /dev/null +++ b/frontend/src/pages/EventTimeline.tsx @@ -0,0 +1,140 @@ +import React, { useState, useEffect } from 'react'; +import { AnimatePresence } from 'framer-motion'; +import { MaterialIcon } from '@/components/MaterialIcon'; +import { useEventTimeline } from '@/hooks/useEventTimeline'; +import { EventTimelineCard } from '@/components/timeline/EventTimelineCard'; +import { EventTimelineFilterBar } from '@/components/timeline/EventTimelineFilterBar'; +import { HighPriorityIncidentBanner } from '@/components/timeline/HighPriorityIncidentBanner'; + +export const EventTimelinePage: React.FC = () => { + const { + events, + rawEventsCount, + filters, + streamStatus, + stats, + unacknowledgedCriticalEvents, + setFilter, + resetFilters, + togglePauseStream, + acknowledgeEvent, + simulateIncident, + } = useEventTimeline(); + + const [utcClock, setUtcClock] = useState(''); + + useEffect(() => { + const updateClock = () => { + setUtcClock(new Date().toUTCString().replace('GMT', 'UTC')); + }; + updateClock(); + const interval = setInterval(updateClock, 1000); + return () => clearInterval(interval); + }, []); + + return ( +
+ {/* Top Page Header */} +
+
+
+ + + REAL-TIME MISSION STREAM + +
+ +

+ + LIVE EVENT TIMELINE +

+

+ Continuous orbital activity stream, conjunction alerts, maneuver tracking & space weather monitoring. +

+
+ + {/* Live Metrics Summary Cards */} +
+
+

TOTAL EVENTS

+

+ {stats.total} +

+
+ +
+

HIGH PRIORITY

+

0 ? 'text-status-emergency animate-pulse' : 'text-status-warning' + }`} + > + {stats.critical + stats.high} +

+
+ +
+

STREAM STATUS

+

+ {streamStatus} +

+
+ +
+

UTC TIME

+

+ {utcClock.substring(17, 25) || '18:20:00 UTC'} +

+
+
+
+ + {/* High-Priority Critical Alert Banner */} + + + {/* Toolbar Filter Controls */} + + + {/* Main Timeline Events Stream List */} +
+ {events.length === 0 ? ( +
+ +

+ NO EVENTS MATCH CURRENT FILTERS +

+

+ Try adjusting your category, severity, time range, or search query to display incoming orbital events. +

+ +
+ ) : ( + + {events.map((evt) => ( + + ))} + + )} +
+
+ ); +}; + +export default EventTimelinePage; diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index c850cbc..8dc50c1 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -183,6 +183,15 @@ export const api = { triggerCollisionEvaluation: () => // eslint-disable-next-line @typescript-eslint/no-explicit-any apiFetch('/collisions/evaluate', { method: 'POST' }), + + getEvents: (params: { category?: string; severity?: string; limit?: number; search?: string } = {}) => { + const q = new URLSearchParams(); + if (params.category) q.set('category', params.category); + if (params.severity) q.set('severity', params.severity); + if (params.limit) q.set('limit', String(params.limit)); + if (params.search) q.set('search', params.search); + return apiFetch(`/events?${q}`); + }, }; diff --git a/frontend/src/store/eventTimelineStore.ts b/frontend/src/store/eventTimelineStore.ts new file mode 100644 index 0000000..76c9cf3 --- /dev/null +++ b/frontend/src/store/eventTimelineStore.ts @@ -0,0 +1,340 @@ +import { create } from 'zustand'; +import type { + TimelineEvent, + EventFilterParams, + TimelineEventCategory, + EventSeverity, +} from '@/types/events'; +import { logEvent } from './logbookStore'; + +const INITIAL_EVENTS: TimelineEvent[] = [ + { + id: 'evt_init_101', + timestamp: new Date(Date.now() - 3 * 60 * 1000).toISOString(), + category: 'CONJUNCTION', + severity: 'CRITICAL', + title: 'CRITICAL CONJUNCTION RISK: ISS (ZARYA) vs DEBRIS 2021-055A', + description: 'Close-approach predicted within key warning sphere. Collision probability exceeds emergency response threshold.', + satellite_name: 'ISS (ZARYA)', + norad_id: '25544', + cospar_id: '1998-067A', + is_high_priority: true, + acknowledged: false, + telemetry: { + miss_distance_m: 142.5, + collision_probability: 0.0342, + relative_velocity_kms: 14.2, + orbit_altitude_km: 418.6, + }, + external_references: [ + { label: 'Space-Track Conjunction Data', url: 'https://www.space-track.org' }, + { label: 'CelesTrak CDM', url: 'https://celestrak.org' }, + ], + }, + { + id: 'evt_init_102', + timestamp: new Date(Date.now() - 15 * 60 * 1000).toISOString(), + category: 'SPACE_WEATHER', + severity: 'HIGH', + title: 'X1.2-CLASS SOLAR FLARE & GEOMAGNETIC STORM WARNING', + description: 'Active region AR3664 produced an X1.2 solar flare with an associated Earth-directed Coronal Mass Ejection (CME).', + satellite_name: 'GLOBAL WEATHER MONITOR', + norad_id: '43012', + is_high_priority: true, + acknowledged: false, + telemetry: { + kp_index: 7.3, + solar_flux_sfu: 245.8, + }, + external_references: [ + { label: 'NOAA Space Weather Prediction Center', url: 'https://www.swpc.noaa.gov' }, + ], + }, + { + id: 'evt_init_103', + timestamp: new Date(Date.now() - 42 * 60 * 1000).toISOString(), + category: 'MANEUVER', + severity: 'MEDIUM', + title: 'COLLISION AVOIDANCE MANEUVER EXECUTED — SENTINEL-6A', + description: 'Thrust duration 14.2s completed successfully. Perigee raised by +420m to clear debris field path.', + satellite_name: 'SENTINEL-6A', + norad_id: '46984', + cospar_id: '2020-086A', + is_high_priority: false, + acknowledged: true, + telemetry: { + delta_v_ms: 0.42, + fuel_cost_kg: 1.84, + orbit_altitude_km: 1336.2, + velocity_kms: 7.21, + }, + external_references: [ + { label: 'ESA Copernicus Operations', url: 'https://www.esa.int' }, + ], + }, + { + id: 'evt_init_104', + timestamp: new Date(Date.now() - 90 * 60 * 1000).toISOString(), + category: 'LAUNCH', + severity: 'LOW', + title: 'ORBITAL INSERTION CONFIRMED — STARLINK-G8-12 BATCH', + description: 'Falcon 9 second stage deployment nominal. 23 spacecraft inserted into 290 km initial checkout orbit.', + satellite_name: 'STARLINK-G8-12', + norad_id: '59102', + cospar_id: '2026-014A', + is_high_priority: false, + acknowledged: true, + telemetry: { + orbit_altitude_km: 290.4, + inclination_deg: 53.2, + velocity_kms: 7.73, + }, + }, + { + id: 'evt_init_105', + timestamp: new Date(Date.now() - 140 * 60 * 1000).toISOString(), + category: 'DEBRIS', + severity: 'HIGH', + title: 'NEW DEBRIS FRAGMENTATION EVENT DETECTED', + description: 'Breakup alert in LEO orbit (720 km). 48 new trackable object vectors registered by Ground Radar Network.', + satellite_name: 'COSMOS-1408 FRAGMENT CLUSTER', + norad_id: '49812', + is_high_priority: true, + acknowledged: false, + telemetry: { + fragment_count: 48, + orbit_altitude_km: 720.5, + }, + external_references: [ + { label: 'EU SST Tracking Alert', url: 'https://www.eusst.eu' }, + ], + }, + { + id: 'evt_init_106', + timestamp: new Date(Date.now() - 210 * 60 * 1000).toISOString(), + category: 'SYSTEM', + severity: 'LOW', + title: 'GROUND RADAR ALPHA TELEMETRY SYNCHRONIZATION', + description: 'Radar calibration completed. Orbital element propagation accuracy increased by 14.8%.', + is_high_priority: false, + acknowledged: true, + }, +]; + +const GENERATOR_TEMPLATES = [ + { + category: 'CONJUNCTION' as TimelineEventCategory, + severity: 'HIGH' as EventSeverity, + title: (sat: string, deb: string) => `CONJUNCTION ALERT: ${sat} vs ${deb}`, + description: 'Automated screening identified close approach within critical clearance distance.', + getTelemetry: () => ({ + miss_distance_m: Math.floor(Math.random() * 800 + 100), + collision_probability: Number((Math.random() * 0.02 + 0.001).toFixed(4)), + relative_velocity_kms: Number((Math.random() * 5 + 10).toFixed(1)), + orbit_altitude_km: Math.floor(Math.random() * 600 + 400), + }), + }, + { + category: 'MANEUVER' as TimelineEventCategory, + severity: 'MEDIUM' as EventSeverity, + title: (sat: string) => `STATION-KEEPING BURN COMPLETED — ${sat}`, + description: 'Electric propulsion thrusters fired for 180s to counteract atmospheric drag decay.', + getTelemetry: () => ({ + delta_v_ms: Number((Math.random() * 0.2 + 0.05).toFixed(2)), + fuel_cost_kg: Number((Math.random() * 0.5 + 0.1).toFixed(2)), + orbit_altitude_km: Math.floor(Math.random() * 300 + 500), + }), + }, + { + category: 'SPACE_WEATHER' as TimelineEventCategory, + severity: 'MEDIUM' as EventSeverity, + title: () => 'GEOMAGNETIC FLUCTUATION DETECTED (Kp 5.8)', + description: 'Increased ionospheric drag anticipated for satellites operating below 500km altitude.', + getTelemetry: () => ({ + kp_index: Number((Math.random() * 2 + 5).toFixed(1)), + solar_flux_sfu: Math.floor(Math.random() * 50 + 180), + }), + }, + { + category: 'DEBRIS' as TimelineEventCategory, + severity: 'LOW' as EventSeverity, + title: (sat: string) => `DEBRIS CATALOG RE-ENTRY UPDATED — ${sat}`, + description: 'Radar tracking confirmed atmospheric decay trajectory within nominal re-entry corridor.', + getTelemetry: () => ({ + orbit_altitude_km: Math.floor(Math.random() * 80 + 120), + }), + }, + { + category: 'LAUNCH' as TimelineEventCategory, + severity: 'LOW' as EventSeverity, + title: (sat: string) => `PAYLOAD SEPARATION CONFIRMED — ${sat}`, + description: 'Telemetry indicates solar array deployment sequence initiated successfully.', + getTelemetry: () => ({ + orbit_altitude_km: Math.floor(Math.random() * 200 + 500), + inclination_deg: Number((Math.random() * 40 + 45).toFixed(1)), + }), + }, +]; + +const SATELLITE_POOL = [ + 'HST (HUBBLE)', + 'NOAA-19', + 'TERRA (EOS AM-1)', + 'AQUA (EOS PM-1)', + 'LANDSAT-9', + 'ENVISAT', + 'CRYOSAT-2', + 'TIANGONG STATION', +]; + +const DEBRIS_POOL = [ + 'FENGYUN 1C DEBRIS', + 'COSMOS 2251 DEBRIS', + 'IRIDIUM 33 DEBRIS', + 'SL-16 R/B DEBRIS', + 'TITAN 3C DEBRIS', +]; + +export interface EventTimelineState { + events: TimelineEvent[]; + filters: EventFilterParams; + streamStatus: 'LIVE' | 'PAUSED' | 'DISCONNECTED'; + streamSpeed: number; // 1x, 2x, 5x + selectedEventId: string | null; + + // Actions + addEvent: (event: Omit & { timestamp?: string }) => void; + setFilter: (key: K, value: EventFilterParams[K]) => void; + resetFilters: () => void; + togglePauseStream: () => void; + setStreamSpeed: (speed: number) => void; + setSelectedEventId: (id: string | null) => void; + acknowledgeEvent: (id: string) => void; + simulateIncident: () => void; + clearAllEvents: () => void; + startLiveStreaming: () => () => void; +} + +export const DEFAULT_FILTERS: EventFilterParams = { + category: 'ALL', + severity: 'ALL', + timeRange: 'ALL', + searchQuery: '', + sortOrder: 'NEWEST_FIRST', +}; + +let eventCounter = 1000; + +export const useEventTimelineStore = create((set, get) => ({ + events: INITIAL_EVENTS, + filters: DEFAULT_FILTERS, + streamStatus: 'LIVE', + streamSpeed: 1, + selectedEventId: null, + + addEvent: (eventInput) => { + eventCounter += 1; + const newEvent: TimelineEvent = { + ...eventInput, + id: `evt_live_${Date.now()}_${eventCounter}`, + timestamp: eventInput.timestamp || new Date().toISOString(), + acknowledged: eventInput.acknowledged ?? false, + }; + + set((state) => { + const updated = [newEvent, ...state.events]; + // Keep up to 200 events in memory + return { events: updated.slice(0, 200) }; + }); + + // Record in central operational logbook if HIGH or CRITICAL + if (newEvent.severity === 'HIGH' || newEvent.severity === 'CRITICAL') { + logEvent( + 'ALERTS', + newEvent.severity, + newEvent.title, + newEvent.description, + { + CATEGORY: newEvent.category, + NORAD_ID: newEvent.norad_id || 'N/A', + SATELLITE: newEvent.satellite_name || 'N/A', + } + ); + } + }, + + setFilter: (key, value) => + set((state) => ({ + filters: { ...state.filters, [key]: value }, + })), + + resetFilters: () => set({ filters: DEFAULT_FILTERS }), + + togglePauseStream: () => + set((state) => ({ + streamStatus: state.streamStatus === 'LIVE' ? 'PAUSED' : 'LIVE', + })), + + setStreamSpeed: (speed) => set({ streamSpeed: speed }), + + setSelectedEventId: (id) => set({ selectedEventId: id }), + + acknowledgeEvent: (id) => + set((state) => ({ + events: state.events.map((evt) => + evt.id === id ? { ...evt, acknowledged: true } : evt + ), + })), + + simulateIncident: () => { + const sat = SATELLITE_POOL[Math.floor(Math.random() * SATELLITE_POOL.length)]; + const deb = DEBRIS_POOL[Math.floor(Math.random() * DEBRIS_POOL.length)]; + get().addEvent({ + category: 'CONJUNCTION', + severity: 'CRITICAL', + title: `EMERGENCY CONJUNCTION WARNING: ${sat} vs ${deb}`, + description: 'HIGH COLLISION PROBABILITY DETECTED! TCA estimated in less than 90 minutes. Immediate evasion calculation required.', + satellite_name: sat, + norad_id: String(Math.floor(Math.random() * 30000 + 20000)), + is_high_priority: true, + acknowledged: false, + telemetry: { + miss_distance_m: Math.floor(Math.random() * 80 + 20), + collision_probability: Number((Math.random() * 0.08 + 0.02).toFixed(4)), + relative_velocity_kms: 14.8, + orbit_altitude_km: 512, + }, + external_references: [ + { label: 'Space-Track Urgent CDM', url: 'https://www.space-track.org' }, + ], + }); + }, + + clearAllEvents: () => set({ events: [] }), + + startLiveStreaming: () => { + const intervalTime = 12000; // Generate event every 12 seconds when streaming + const timer = setInterval(() => { + const { streamStatus, addEvent } = get(); + if (streamStatus !== 'LIVE') return; + + const template = GENERATOR_TEMPLATES[Math.floor(Math.random() * GENERATOR_TEMPLATES.length)]; + const sat = SATELLITE_POOL[Math.floor(Math.random() * SATELLITE_POOL.length)]; + const deb = DEBRIS_POOL[Math.floor(Math.random() * DEBRIS_POOL.length)]; + + addEvent({ + category: template.category, + severity: template.severity, + title: template.title(sat, deb), + description: template.description, + satellite_name: sat, + norad_id: String(Math.floor(Math.random() * 40000 + 10000)), + is_high_priority: template.severity === 'HIGH' || template.severity === 'CRITICAL', + acknowledged: false, + telemetry: template.getTelemetry(), + }); + }, intervalTime); + + return () => clearInterval(timer); + }, +})); diff --git a/frontend/src/types/events.ts b/frontend/src/types/events.ts new file mode 100644 index 0000000..5705e1d --- /dev/null +++ b/frontend/src/types/events.ts @@ -0,0 +1,65 @@ +/** + * Types for the Live Mission Control Event Timeline. + * + * Tracks real-time mission & orbital activities including launches, + * conjunction predictions, maneuver executions, debris tracking, + * and space weather alerts. + */ + +export type TimelineEventCategory = + | 'LAUNCH' + | 'CONJUNCTION' + | 'MANEUVER' + | 'DEBRIS' + | 'SPACE_WEATHER' + | 'SYSTEM'; + +export type EventSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; + +export interface ExternalReference { + label: string; + url: string; +} + +export interface EventTelemetry { + miss_distance_m?: number; + collision_probability?: number; + relative_velocity_kms?: number; + orbit_altitude_km?: number; + velocity_kms?: number; + delta_v_ms?: number; + fuel_cost_kg?: number; + kp_index?: number; + solar_flux_sfu?: number; + fragment_count?: number; + inclination_deg?: number; + [key: string]: string | number | boolean | undefined; +} + +export interface TimelineEvent { + id: string; + timestamp: string; // ISO UTC string + category: TimelineEventCategory; + severity: EventSeverity; + title: string; + description: string; + satellite_name?: string; + norad_id?: string; + cospar_id?: string; + telemetry?: EventTelemetry; + external_references?: ExternalReference[]; + is_high_priority?: boolean; + acknowledged?: boolean; +} + +export type TimeRangeFilter = '1H' | '24H' | '7D' | 'ALL'; + +export interface EventFilterParams { + category: TimelineEventCategory | 'ALL'; + severity: EventSeverity | 'ALL'; + timeRange: TimeRangeFilter; + searchQuery: string; + noradId?: string; + sortOrder: 'NEWEST_FIRST' | 'OLDEST_FIRST'; + highPriorityOnly?: boolean; +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index 3f21f57..38694ff 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -2,14 +2,21 @@ "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "es2023", - "lib": ["ES2023", "DOM"], + "lib": [ + "ES2023", + "DOM" + ], "module": "esnext", - "types": ["vite/client", "node"], + "types": [ + "vite/client", + "node" + ], "skipLibCheck": true, "baseUrl": ".", - "ignoreDeprecations": "6.0", "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] }, /* Bundler mode */ "moduleResolution": "bundler", @@ -18,12 +25,13 @@ "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", - /* Linting */ "noUnusedLocals": false, "noUnusedParameters": false, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"] -} + "include": [ + "src" + ] +} \ No newline at end of file