import React, { useState, useEffect } from 'react';
import { MapVisualization } from '@/components/MapVisualization';
import ConnectionStatus from '@/components/ConnectionStatus';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Map, RefreshCw, AlertCircle } from 'lucide-react';
interface Robot {
robot_id: string;
robot_type: string;
name?: string;
position?: { x: number; y: number; z?: number };
rotation?: { yaw: number };
connected: boolean;
status?: string;
battery?: number;
}
interface MapData {
map_id?: string;
rooms?: Array<{
id: string;
name: string;
coordinates: number[];
type?: string;
}>;
obstacles?: Array<{
x: number;
y: number;
size?: number;
type?: string;
}>;
charging_station?: {
x: number;
y: number;
orientation?: number;
};
}
export default function MapPage() {
const [robots, setRobots] = useState<Robot[]>([]);
const [mapData, setMapData] = useState<MapData | undefined>();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [connectionStatus, setConnectionStatus] = useState<{
backend: boolean;
robotics_mcp: boolean;
}>({ backend: false, robotics_mcp: false });
useEffect(() => {
loadData();
const interval = setInterval(loadData, 5000); // Refresh every 5 seconds
return () => clearInterval(interval);
}, []);
const loadData = async () => {
try {
setError(null);
// Load robots
const robotsResponse = await fetch('/api/robots');
if (robotsResponse.ok) {
const robotsData = await robotsResponse.json();
setRobots(robotsData.robots || []);
setConnectionStatus(prev => ({ ...prev, backend: true }));
} else {
throw new Error('Failed to load robots');
}
// Load map data from Dreame robot (if available)
const dreameRobot = robots.find(r => r.robot_type === 'dreame' && r.connected);
if (dreameRobot) {
try {
const mapResponse = await fetch(`/api/robots/${dreameRobot.robot_id}/control`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'get_map' })
});
if (mapResponse.ok) {
const result = await mapResponse.json();
if (result.success && result.data) {
setMapData(result.data.map_data || result.data.map);
}
}
} catch (mapError) {
console.warn('Failed to load map data:', mapError);
}
}
setConnectionStatus(prev => ({ ...prev, robotics_mcp: true }));
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
setConnectionStatus({ backend: false, robotics_mcp: false });
} finally {
setLoading(false);
}
};
const handleRobotCommand = async (robotId: string, command: any) => {
try {
const response = await fetch(`/api/robots/${robotId}/control`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(command)
});
if (!response.ok) {
throw new Error('Command failed');
}
// Refresh data after command
setTimeout(loadData, 1000);
} catch (err) {
console.error('Robot command failed:', err);
}
};
const getRobotStats = () => {
const total = robots.length;
const connected = robots.filter(r => r.connected).length;
const withPosition = robots.filter(r => r.position).length;
return { total, connected, withPosition };
};
const stats = getRobotStats();
return (
<div className="container mx-auto p-6 space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold flex items-center gap-2">
<Map className="w-8 h-8 text-blue-500" />
Robotics Map Control Center
</h1>
<p className="text-gray-600 mt-2">
Real-time LIDAR visualization and multi-robot coordination
</p>
</div>
<div className="flex items-center gap-4">
<ConnectionStatus
backend={connectionStatus.backend}
robotics_mcp={connectionStatus.robotics_mcp}
/>
<Button onClick={loadData} disabled={loading}>
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
Refresh
</Button>
</div>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{stats.total}</div>
<div className="text-sm text-gray-600">Total Robots</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold text-green-600">{stats.connected}</div>
<div className="text-sm text-gray-600">Connected</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold text-blue-600">{stats.withPosition}</div>
<div className="text-sm text-gray-600">With Position</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold text-purple-600">
{mapData ? 'Available' : 'No Map'}
</div>
<div className="text-sm text-gray-600">LIDAR Map</div>
</CardContent>
</Card>
</div>
{/* Robot Status Overview */}
<Card>
<CardHeader>
<CardTitle>Robot Status Overview</CardTitle>
<CardDescription>Current status of all connected robots</CardDescription>
</CardHeader>
<CardContent>
{robots.length === 0 ? (
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>No robots connected. Start the robotics MCP server and configure your robots.</AlertDescription>
</Alert>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{robots.map(robot => (
<Card key={robot.robot_id} className={`p-4 ${robot.connected ? 'border-green-200' : 'border-red-200'}`}>
<div className="flex items-center justify-between mb-2">
<h3 className="font-medium">{robot.name || robot.robot_id}</h3>
<Badge variant={robot.connected ? "default" : "destructive"}>
{robot.connected ? "Online" : "Offline"}
</Badge>
</div>
<div className="space-y-1 text-sm">
<div className="flex justify-between">
<span>Type:</span>
<span className="capitalize">{robot.robot_type}</span>
</div>
{robot.position && (
<div className="flex justify-between">
<span>Position:</span>
<span>({robot.position.x.toFixed(2)}, {robot.position.y.toFixed(2)})</span>
</div>
)}
{robot.battery !== undefined && (
<div className="flex justify-between">
<span>Battery:</span>
<span>{robot.battery}%</span>
</div>
)}
{robot.status && (
<div className="flex justify-between">
<span>Status:</span>
<span className="capitalize">{robot.status}</span>
</div>
)}
</div>
</Card>
))}
</div>
)}
</CardContent>
</Card>
{/* Map Visualization */}
<MapVisualization
robots={robots}
mapData={mapData}
onRobotCommand={handleRobotCommand}
className="w-full"
/>
{/* Error Display */}
{error && (
<Alert className="border-red-200">
<AlertCircle className="h-4 w-4" />
<AlertDescription className="text-red-800">
{error}
</AlertDescription>
</Alert>
)}
</div>
);
}