/** * App.jsx — Saltybot Social + Telemetry + Fleet + Mission Dashboard root component. * * Social tabs (issue #107): * Status | Faces | Conversation | Personality | Navigation * * Telemetry tabs (issue #126): * IMU | Battery | Motors | Map | Control | Health * * Fleet tabs (issue #139): * Fleet (self-contained via useFleet) * * Mission tabs (issue #145): * Missions (waypoint editor, route builder, geofence, schedule, execute) */ import { useState, useCallback } from 'react'; import { useRosbridge } from './hooks/useRosbridge.js'; // Social panels import { StatusPanel } from './components/StatusPanel.jsx'; import { FaceGallery } from './components/FaceGallery.jsx'; import { ConversationLog } from './components/ConversationLog.jsx'; import { PersonalityTuner } from './components/PersonalityTuner.jsx'; import { NavModeSelector } from './components/NavModeSelector.jsx'; // Telemetry panels import { ImuPanel } from './components/ImuPanel.jsx'; import { BatteryPanel } from './components/BatteryPanel.jsx'; import { MotorPanel } from './components/MotorPanel.jsx'; import { MapViewer } from './components/MapViewer.jsx'; import { ControlMode } from './components/ControlMode.jsx'; import { SystemHealth } from './components/SystemHealth.jsx'; // Fleet panel (issue #139) import { FleetPanel } from './components/FleetPanel.jsx'; // Mission planner (issue #145) import { MissionPlanner } from './components/MissionPlanner.jsx'; // Settings panel (issue #160) import { SettingsPanel } from './components/SettingsPanel.jsx'; const TAB_GROUPS = [ { label: 'SOCIAL', color: 'text-cyan-600', tabs: [ { id: 'status', label: 'Status', }, { id: 'faces', label: 'Faces', }, { id: 'conversation', label: 'Convo', }, { id: 'personality', label: 'Personality', }, { id: 'navigation', label: 'Nav Mode', }, ], }, { label: 'TELEMETRY', color: 'text-amber-600', tabs: [ { id: 'imu', label: 'IMU', }, { id: 'battery', label: 'Battery', }, { id: 'motors', label: 'Motors', }, { id: 'map', label: 'Map', }, { id: 'control', label: 'Control', }, { id: 'health', label: 'Health', }, ], }, { label: 'FLEET', color: 'text-green-600', tabs: [ { id: 'fleet', label: 'Fleet' }, { id: 'missions', label: 'Missions' }, ], }, { label: 'CONFIG', color: 'text-purple-600', tabs: [ { id: 'settings', label: 'Settings' }, ], }, ]; const FLEET_TABS = new Set(['fleet', 'missions']); const DEFAULT_WS_URL = 'ws://localhost:9090'; function ConnectionBar({ url, setUrl, connected, error }) { const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(url); const handleApply = () => { setUrl(draft); setEditing(false); }; return (
{editing ? (
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleApply(); if (e.key === 'Escape') setEditing(false); }} autoFocus className="bg-gray-900 border border-cyan-800 rounded px-2 py-0.5 text-cyan-300 w-52 focus:outline-none" />
) : ( )}
); } export default function App() { const [wsUrl, setWsUrl] = useState(DEFAULT_WS_URL); const [activeTab, setActiveTab] = useState('status'); const { connected, error, subscribe, publish, callService, setParam } = useRosbridge(wsUrl); const publishFn = useCallback((name, type, data) => publish(name, type, data), [publish]); return (
{/* ── Top Bar ── */}
⚡ SALTYBOT DASHBOARD
{!FLEET_TABS.has(activeTab) && ( )}
{/* ── Tab Navigation ── */} {/* ── Content ── */}
{activeTab === 'status' && } {activeTab === 'faces' && } {activeTab === 'conversation' && } {activeTab === 'personality' && } {activeTab === 'navigation' && } {activeTab === 'imu' && } {activeTab === 'battery' && } {activeTab === 'motors' && } {activeTab === 'map' && } {activeTab === 'control' && } {activeTab === 'health' && } {activeTab === 'fleet' && } {activeTab === 'missions' && } {activeTab === 'settings' && }
{/* ── Footer ── */}
); }