diff --git a/VesperPlus.png b/VesperPlus.png new file mode 100644 index 0000000..57797e1 Binary files /dev/null and b/VesperPlus.png differ diff --git a/backend/devices/models.py b/backend/devices/models.py index 32f2a39..a506b1d 100644 --- a/backend/devices/models.py +++ b/backend/devices/models.py @@ -125,6 +125,7 @@ class DeviceCreate(BaseModel): user_list: List[str] = [] websocket_url: str = "" churchAssistantURL: str = "" + staffNotes: str = "" class DeviceUpdate(BaseModel): @@ -142,6 +143,7 @@ class DeviceUpdate(BaseModel): user_list: Optional[List[str]] = None websocket_url: Optional[str] = None churchAssistantURL: Optional[str] = None + staffNotes: Optional[str] = None class DeviceInDB(DeviceCreate): diff --git a/frontend/public/devices/VesperPlus.png b/frontend/public/devices/VesperPlus.png new file mode 100644 index 0000000..57797e1 Binary files /dev/null and b/frontend/public/devices/VesperPlus.png differ diff --git a/frontend/src/devices/DeviceDetail.jsx b/frontend/src/devices/DeviceDetail.jsx index 57190a3..ea1fef6 100644 --- a/frontend/src/devices/DeviceDetail.jsx +++ b/frontend/src/devices/DeviceDetail.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { MapContainer, TileLayer, Marker } from "react-leaflet"; import "leaflet/dist/leaflet.css"; @@ -456,50 +456,65 @@ export default function DeviceDetail() { // ===== Section definitions ===== + const [staffNotes, setStaffNotes] = useState(device.staffNotes || ""); + const [editingNotes, setEditingNotes] = useState(false); + const [savingNotes, setSavingNotes] = useState(false); + const notesRef = useRef(null); + + const saveStaffNotes = async (value) => { + setSavingNotes(true); + try { + await api.put(`/devices/${id}`, { staffNotes: value }); + setStaffNotes(value); + } catch { + // silently fail + } finally { + setSavingNotes(false); + setEditingNotes(false); + } + }; + + // Hardware variant to image mapping + const hwImageMap = { + VesperPlus: "/devices/VesperPlus.png", + }; + const hwVariant = "VesperPlus"; // TODO: read from device data when available + const hwImage = hwImageMap[hwVariant] || hwImageMap.VesperPlus; + const deviceInfoSection = (

Device Information

- {/* Col 1: Status — fancy card filling vertical space */} -
- - {isOnline ? "ON" : "OFF"} - -
+ {/* Col 1: Status on top, device image below */} +
+
-
Status
-
- {isOnline ? "Online" : "Offline"} -
- {mqttStatus && ( -
- {mqttStatus.seconds_since_heartbeat}s ago +
+
Status
+
+ {isOnline ? "Online" : "Offline"} + {mqttStatus && ( + + {mqttStatus.seconds_since_heartbeat}s ago + + )}
- )} +
+
+
+ {hwVariant}
@@ -519,10 +534,62 @@ export default function DeviceDetail() {
- {/* Col 3: Admin Notes */} + {/* Col 3: Admin Notes — editable */}
-
Admin Notes
-
-
+
+
Admin Notes
+ {!editingNotes && ( + + )} +
+ {editingNotes ? ( +
+