"use client"; import { useEffect, useState } from "react"; import { useSession } from "next-auth/react"; import { Geofence } from "@/lib/types"; interface GeofenceWithDevice extends Geofence { deviceName?: string; } interface Device { id: string; name: string; } export default function GeofencesPage() { const { data: session } = useSession(); const userRole = (session?.user as any)?.role; const userId = (session?.user as any)?.id; const isAdmin = userRole === 'ADMIN'; const [geofences, setGeofences] = useState([]); const [devices, setDevices] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showAddModal, setShowAddModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [selectedGeofence, setSelectedGeofence] = useState(null); const [formData, setFormData] = useState({ name: "", description: "", device_id: "", center_latitude: "", center_longitude: "", radius_meters: "500", color: "#10b981", }); useEffect(() => { fetchGeofences(); fetchDevices(); // Auto-refresh every 30 seconds const interval = setInterval(fetchGeofences, 30000); return () => clearInterval(interval); }, []); const fetchGeofences = async () => { try { const response = await fetch("/api/geofences"); if (!response.ok) throw new Error("Failed to fetch geofences"); const data = await response.json(); setGeofences(data.geofences || []); setError(null); } catch (err) { console.error("Failed to fetch geofences", err); setError("Failed to load geofences"); } finally { setLoading(false); } }; 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 || []); } catch (err) { console.error("Failed to fetch devices", err); } }; const handleAdd = async (e: React.FormEvent) => { e.preventDefault(); try { const response = await fetch("/api/geofences", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: formData.name, description: formData.description || undefined, device_id: formData.device_id, center_latitude: parseFloat(formData.center_latitude), center_longitude: parseFloat(formData.center_longitude), radius_meters: parseInt(formData.radius_meters), color: formData.color, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to create geofence"); } await fetchGeofences(); setShowAddModal(false); setFormData({ name: "", description: "", device_id: "", center_latitude: "", center_longitude: "", radius_meters: "500", color: "#10b981", }); } catch (err: any) { alert(err.message); } }; const handleEdit = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedGeofence) return; try { const response = await fetch(`/api/geofences/${selectedGeofence.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: formData.name, description: formData.description || undefined, radius_meters: parseInt(formData.radius_meters), color: formData.color, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to update geofence"); } await fetchGeofences(); setShowEditModal(false); setSelectedGeofence(null); } catch (err: any) { alert(err.message); } }; const openEditModal = (geofence: Geofence) => { setSelectedGeofence(geofence); setFormData({ name: geofence.name, description: geofence.description || "", device_id: geofence.device_id, center_latitude: geofence.center_latitude.toString(), center_longitude: geofence.center_longitude.toString(), radius_meters: geofence.radius_meters.toString(), color: geofence.color, }); setShowEditModal(true); }; const handleDelete = async (geofence: Geofence) => { if (!confirm(`Delete geofence "${geofence.name}"? This will also delete all associated events.`)) { return; } try { const response = await fetch(`/api/geofences/${geofence.id}`, { method: "DELETE", }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to delete geofence"); } await fetchGeofences(); } catch (err: any) { alert(err.message); } }; const toggleActive = async (geofence: Geofence) => { try { const response = await fetch(`/api/geofences/${geofence.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ is_active: geofence.is_active === 1 ? 0 : 1 }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to update geofence"); } await fetchGeofences(); } catch (err: any) { alert(err.message); } }; // Calculate zone limit for current user const zoneLimit = isAdmin ? null : 5; const canCreateMore = zoneLimit === null || geofences.length < zoneLimit; if (loading) { return (

Loading geofences...

); } return (
{/* Hero Section */}

Geofences

Manage your location-based zones and alerts

{zoneLimit !== null && (
{geofences.length} / {zoneLimit} zones used
)}
{/* Error Message */} {error && (

{error}

)} {/* Geofences Table */}
πŸ“

Your Geofences

{geofences.length === 0 ? (
πŸ—ΊοΈ

No geofences yet

Create your first geofence to get notified when devices enter or exit zones

) : (
{geofences.map((geofence) => ( ))}
Name Device Status Radius Location Created Actions
{geofence.name}
{geofence.description && (
{geofence.description}
)}
{geofence.deviceName || geofence.device_id} {geofence.radius_meters >= 1000 ? `${(geofence.radius_meters / 1000).toFixed(1)} km` : `${geofence.radius_meters} m`} {geofence.center_latitude.toFixed(4)}, {geofence.center_longitude.toFixed(4)} {new Date(geofence.created_at).toLocaleDateString()}
)}
{/* Quick Links */} {/* Add Geofence Modal */} {showAddModal && (

Create New Geofence

setFormData({ ...formData, name: e.target.value })} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="e.g., Home, Office" />