import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import api from '../services/api'; import { format } from 'date-fns'; import { t } from '../utils/tempTranslations'; import { PlusIcon, PencilIcon, TrashIcon, ServerIcon, MapPinIcon, SignalIcon, BoltIcon } from '@heroicons/react/24/outline'; const Devices = () => { const navigate = useNavigate(); const [devices, setDevices] = useState([]); const [loading, setLoading] = useState(true); const [showAddModal, setShowAddModal] = useState(false); const [editingDevice, setEditingDevice] = useState(null); const [filter, setFilter] = useState('all'); // 'all', 'approved', 'pending' const [showDetailsModal, setShowDetailsModal] = useState(false); const [selectedDevice, setSelectedDevice] = useState(null); useEffect(() => { fetchDevices(); }, []); const fetchDevices = async () => { try { const response = await api.get('/devices?include_stats=true'); setDevices(response.data.data); } catch (error) { console.error('Error fetching devices:', error); } finally { setLoading(false); } }; const handleAddDevice = () => { setEditingDevice(null); setShowAddModal(true); }; const handleEditDevice = (device) => { setEditingDevice(device); setShowAddModal(true); }; const handleApproveDevice = async (deviceId) => { try { await api.post(`/devices/${deviceId}/approve`, { approved: true }); fetchDevices(); } catch (error) { console.error('Error approving device:', error); if (error.response?.status === 401 || error.response?.status === 403) { alert('Your session has expired. Please log in again.'); return; } alert('Error approving device: ' + (error.response?.data?.message || error.message)); } }; const handleRejectDevice = async (deviceId) => { if (window.confirm(t('devices.confirmReject'))) { try { await api.post(`/devices/${deviceId}/approve`, { approved: false }); fetchDevices(); } catch (error) { console.error('Error rejecting device:', error); if (error.response?.status === 401 || error.response?.status === 403) { alert('Your session has expired. Please log in again.'); return; } alert(t('devices.errorRejecting') + ' ' + (error.response?.data?.message || error.message)); } } }; const handleViewDetails = (device) => { setSelectedDevice(device); setShowDetailsModal(true); }; const handleViewOnMap = (device) => { if (device.geo_lat && device.geo_lon) { // Navigate to map with device information navigate('/map', { state: { focusDevice: { id: device.id, name: device.name || `Device ${device.id}`, lat: device.geo_lat, lon: device.geo_lon, status: device.stats?.status || 'unknown' } } }); } else { alert('Device location coordinates are not available'); } }; const handleDeleteDevice = async (deviceId) => { if (window.confirm(t('devices.confirmDelete'))) { try { await api.delete(`/devices/${deviceId}`); fetchDevices(); } catch (error) { console.error(t('devices.errorDeleting'), error); } } }; const getStatusColor = (status) => { switch (status) { case 'online': return 'bg-green-100 text-green-800'; case 'offline': return 'bg-red-100 text-red-800'; default: return 'bg-gray-100 text-gray-800'; } }; const getSignalStrength = (lastHeartbeat) => { if (!lastHeartbeat) return t('devices.unknown'); const timeSince = (new Date() - new Date(lastHeartbeat)) / 1000 / 60; // minutes if (timeSince < 5) return t('devices.signalStrong'); if (timeSince < 15) return t('devices.signalGood'); if (timeSince < 60) return t('devices.signalWeak'); return t('devices.signalLost'); }; const filteredDevices = devices.filter(device => { if (filter === 'approved') return device.is_approved; if (filter === 'pending') return !device.is_approved; return true; // 'all' }); const pendingCount = devices.filter(device => !device.is_approved).length; if (loading) { return (
{t('devices.loading')}
); } return (

{t('devices.title')}

{t('devices.description')} {pendingCount > 0 && ( {pendingCount} {t('devices.pendingApproval')} )}

{/* Filter Tabs */}
{/* Device Grid */}
{filteredDevices.map((device) => (

{device.name || `${t('devices.device')} ${device.id}`}

{!device.is_approved && ( {t('devices.needsApproval')} )}
{t('devices.status')} {device.stats?.status ? t(`devices.${device.stats.status}`) : t('devices.unknown')}
{t('devices.approval')} {device.is_approved ? t('devices.approved') : t('devices.pending')}
{t('devices.deviceId')} {device.id}
{device.location_description && (
{t('devices.location')} {device.location_description}
)} {(device.geo_lat && device.geo_lon) && (
{t('devices.coordinates')} {device.geo_lat}, {device.geo_lon}
)}
{t('devices.signal')} {getSignalStrength(device.last_heartbeat)}
{device.stats && (
{t('devices.detections24h')} 0 ? 'text-red-600' : 'text-green-600' }`}> {device.stats.detections_24h}
)} {device.last_heartbeat && (
{t('devices.lastSeen')} {format(new Date(device.last_heartbeat), 'MMM dd, HH:mm')}
)} {device.firmware_version && (
{t('devices.firmware')} {device.firmware_version}
)}
{/* Device Actions */}
{!device.is_approved ? (
) : null}
))}
{filteredDevices.length === 0 && devices.length > 0 && (

{filter === 'all' ? t('devices.noDevices') : `${t('devices.noDevicesFiltered').replace('the current filter', filter)}`}

{filter === 'pending' ? t('devices.noDevicesPending') : filter === 'approved' ? t('devices.noDevicesApproved') : t('devices.noDevicesFiltered') }

)} {devices.length === 0 && (

{t('devices.noDevices')}

{t('devices.noDevicesDescription')}

)} {/* Add/Edit Device Modal */} {showAddModal && ( setShowAddModal(false)} onSave={() => { setShowAddModal(false); fetchDevices(); }} /> )} {/* Device Details Modal */} {showDetailsModal && selectedDevice && (

{t('devices.deviceDetails')}

{t('devices.deviceId')}: {selectedDevice.id}
{t('devices.name')}: {selectedDevice.name || t('devices.unnamed')}
{t('devices.status')}: {selectedDevice.stats?.status ? t(`devices.${selectedDevice.stats.status}`) : t('devices.unknown')}
{t('devices.approved')}: {selectedDevice.is_approved ? t('devices.yes') : t('devices.pending')}
{selectedDevice.location_description && (
{t('devices.location')}: {selectedDevice.location_description}
)} {(selectedDevice.geo_lat && selectedDevice.geo_lon) && (
{t('devices.coordinates')}: {selectedDevice.geo_lat}, {selectedDevice.geo_lon}
)} {selectedDevice.last_heartbeat && (
{t('devices.lastHeartbeat')}: {format(new Date(selectedDevice.last_heartbeat), 'MMM dd, yyyy HH:mm')}
)} {selectedDevice.firmware_version && (
{t('devices.firmware')}: {selectedDevice.firmware_version}
)} {selectedDevice.stats && (
{t('devices.detections24h')}: 0 ? 'text-red-600' : 'text-green-600' }`}> {selectedDevice.stats.detections_24h}
)} {selectedDevice.created_at && (
{t('alerts.created')}: {format(new Date(selectedDevice.created_at), 'MMM dd, yyyy HH:mm')}
)}
{!selectedDevice.is_approved && ( )} {(selectedDevice.geo_lat && selectedDevice.geo_lon) && ( )}
)}
); }; const DeviceModal = ({ device, onClose, onSave }) => { const [formData, setFormData] = useState({ id: device?.id || '', name: device?.name || '', geo_lat: device?.geo_lat || '', geo_lon: device?.geo_lon || '', location_description: device?.location_description || '', heartbeat_interval: device?.heartbeat_interval || 300, firmware_version: device?.firmware_version || '', notes: device?.notes || '' }); const [saving, setSaving] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); setSaving(true); try { if (device) { // Update existing device - include coordinates and other editable fields const updateData = { name: formData.name, geo_lat: formData.geo_lat ? parseFloat(formData.geo_lat) : null, geo_lon: formData.geo_lon ? parseFloat(formData.geo_lon) : null, location_description: formData.location_description || null, notes: formData.notes || null }; // Remove null values to avoid sending unnecessary data Object.keys(updateData).forEach(key => { if (updateData[key] === null || updateData[key] === '') { delete updateData[key]; } }); await api.put(`/devices/${device.id}`, updateData); } else { // Create new device - include all fields, convert empty strings to null const createData = { ...formData }; Object.keys(createData).forEach(key => { if (createData[key] === '') { createData[key] = null; } }); await api.post('/devices', createData); } onSave(); } catch (error) { console.error('Error saving device:', error); // Check if it's a token expiration error if (error.response?.status === 401 || error.response?.status === 403) { alert('Your session has expired. Please log in again.'); // The API interceptor will handle the logout automatically return; } alert('Error saving device: ' + (error.response?.data?.message || error.message)); } finally { setSaving(false); } }; const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; return (

{device ? 'Edit Device' : 'Add New Device'}

{!device && (
)}