"use client"; import { useEffect, useState } from "react"; import { useSession } from "next-auth/react"; interface Device { id: string; name: string; color: string; description?: string; isActive: boolean; createdAt: string; updatedAt: string; owner?: { id: string; username: string; }; latestLocation?: { latitude: string | number; longitude: string | number; timestamp: string; battery?: number; speed?: number; }; _count?: { locations: number; }; } export default function DevicesPage() { const { data: session } = useSession(); const userRole = (session?.user as any)?.role; const isAdmin = userRole === 'ADMIN'; const [devices, setDevices] = useState([]); const [loading, setLoading] = useState(true); const [showAddModal, setShowAddModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [selectedDevice, setSelectedDevice] = useState(null); const [error, setError] = useState(null); // Form state const [formData, setFormData] = useState({ id: "", name: "", color: "#3498db", description: "", }); useEffect(() => { fetchDevices(); // Auto-refresh every 10 seconds to update online/offline status const interval = setInterval(fetchDevices, 10000); return () => clearInterval(interval); }, []); const fetchDevices = async () => { try { const response = await fetch("/api/devices"); if (!response.ok) throw new Error("Failed to fetch devices"); const data = await response.json(); setDevices(data.devices || []); setError(null); } catch (err) { console.error("Failed to fetch devices", err); setError("Failed to load devices"); } finally { setLoading(false); } }; const handleAdd = async (e: React.FormEvent) => { e.preventDefault(); try { const response = await fetch("/api/devices", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to create device"); } await fetchDevices(); setShowAddModal(false); setFormData({ id: "", name: "", color: "#3498db", description: "" }); } catch (err: any) { alert(err.message); } }; const handleEdit = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedDevice) return; try { // If ID changed, we need to delete old and create new device if (formData.id !== selectedDevice.id) { // Delete old device const deleteResponse = await fetch(`/api/devices/${selectedDevice.id}`, { method: "DELETE", }); if (!deleteResponse.ok) { throw new Error("Failed to delete old device"); } // Create new device with new ID const createResponse = await fetch("/api/devices", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), }); if (!createResponse.ok) { const error = await createResponse.json(); throw new Error(error.error || "Failed to create device with new ID"); } } else { // Just update existing device const response = await fetch(`/api/devices/${selectedDevice.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: formData.name, color: formData.color, description: formData.description, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to update device"); } } await fetchDevices(); setShowEditModal(false); setSelectedDevice(null); } catch (err: any) { alert(err.message); } }; const handleDelete = async () => { if (!selectedDevice) return; try { const response = await fetch(`/api/devices/${selectedDevice.id}`, { method: "DELETE", }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to delete device"); } await fetchDevices(); setShowDeleteModal(false); setSelectedDevice(null); } catch (err: any) { alert(err.message); } }; const openEditModal = (device: Device) => { setSelectedDevice(device); setFormData({ id: device.id, name: device.name, color: device.color, description: device.description || "", }); setShowEditModal(true); }; const openDeleteModal = (device: Device) => { setSelectedDevice(device); setShowDeleteModal(true); }; if (loading) { return (
Loading devices...
); } return (
{/* Hero Section with Gradient */}

Device Management

{!isAdmin ? "Read-only view" : "Verwalte deine Tracking-Geräte"}

{isAdmin && ( )}
{error && (
⚠️ {error}
)} {/* Device Cards */}
{devices.map((device) => { const lastSeen = device.latestLocation ? new Date(device.latestLocation.timestamp) : null; const isRecent = lastSeen ? Date.now() - lastSeen.getTime() < 10 * 60 * 1000 : false; return (
📱

{device.name}

ID: {device.id}

{isRecent ? "🟢 Online" : "⚫ Offline"}
{device.description && (

{device.description}

)} {device.latestLocation && (
🕒 Last Seen: {new Date(device.latestLocation.timestamp).toLocaleString()}
{device.latestLocation.battery !== undefined && (
🔋 Battery: 20 ? 'text-green-600' : 'text-red-600' }`}> {device.latestLocation.battery}%
)} {device.latestLocation.speed !== undefined && (
🚗 Speed: {(Number(device.latestLocation.speed) * 3.6).toFixed(1)} km/h
)}
📍 Location: {Number(device.latestLocation.latitude).toFixed(5)},{" "} {Number(device.latestLocation.longitude).toFixed(5)}
)} {device._count && (
{device._count.locations} location points
)} {isAdmin && (
)}
); })}
{devices.length === 0 && (
📱

Keine Devices gefunden

Füge dein erstes Device hinzu, um zu starten.

)} {/* Add Device Modal */} {showAddModal && (

Add New Device

setFormData({ ...formData, id: e.target.value }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., 12, 13" />

Must match OwnTracks tracker ID (tid)

setFormData({ ...formData, name: e.target.value }) } className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., iPhone 13" />
setFormData({ ...formData, color: e.target.value }) } className="h-10 w-20 rounded border border-gray-300" /> setFormData({ ...formData, color: e.target.value }) } className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="#3498db" />