153 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 'use client';
 | |
| 
 | |
| import axios from "axios";
 | |
| import React, { useState, useEffect, useMemo } from "react";
 | |
| 
 | |
| export type SiteName = string;
 | |
| 
 | |
| interface SiteStatusProps {
 | |
|   selectedSite: string;   // display label (e.g., CRM project_name)
 | |
|   siteId: string;         // canonical id (e.g., CRM Project.name like PROJ-0008)
 | |
|   status?: string;        // CRM status (Open/Completed/On Hold/…)
 | |
|   location: string;
 | |
|   inverterProvider: string;
 | |
|   emergencyContact: string;
 | |
|   lastSyncTimestamp: string;
 | |
| }
 | |
| 
 | |
| const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
 | |
| const WS_URL  = process.env.NEXT_PUBLIC_WS_URL  ?? "ws://localhost:8000/ws";
 | |
| 
 | |
| const SiteStatus = ({
 | |
|   selectedSite,
 | |
|   siteId,
 | |
|   status,
 | |
|   location,
 | |
|   inverterProvider,
 | |
|   emergencyContact,
 | |
|   lastSyncTimestamp,
 | |
| }: SiteStatusProps) => {
 | |
| 
 | |
|   // --- WebSocket to receive MQTT-forwarded messages ---
 | |
|   useEffect(() => {
 | |
|     const ws = new WebSocket(WS_URL);
 | |
| 
 | |
|     ws.onopen = () => console.log("WebSocket connected");
 | |
|     ws.onclose = () => console.log("WebSocket disconnected");
 | |
|     ws.onerror = (e) => console.error("WebSocket error:", e);
 | |
| 
 | |
|     ws.onmessage = (event) => {
 | |
|       // Tip: avoid alert storms; log or toast instead
 | |
|       try {
 | |
|         const data = JSON.parse(event.data);
 | |
|         console.log("WS:", data);
 | |
|       } catch {
 | |
|         console.log("WS raw:", event.data);
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     return () => ws.close();
 | |
|   }, []);
 | |
| 
 | |
|   const [showModal, setShowModal] = useState(false);
 | |
|   const [deviceId, setDeviceId] = useState("");
 | |
|   const [functionType, setFunctionType] = useState<"Grid" | "Solar">("Grid");
 | |
| 
 | |
|   // Track devices connected per siteId (dynamic)
 | |
|   const [loggedDevices, setLoggedDevices] = useState<Record<string, string[]>>({});
 | |
|   const devicesAtSite = loggedDevices[siteId] ?? [];
 | |
| 
 | |
|   const handleStartLogging = () => setShowModal(true);
 | |
| 
 | |
|   const handleConfirm = async () => {
 | |
|     const id = deviceId.trim();
 | |
|     if (!id) return;
 | |
| 
 | |
|     const topic = `ADW300/${siteId}/${id}/${functionType.toLowerCase()}`;
 | |
| 
 | |
|     try {
 | |
|       const response = await axios.post(`${API_URL}/start-logging`, { topics: [topic] });
 | |
|       console.log("Started logging:", response.data);
 | |
| 
 | |
|       setLoggedDevices(prev => ({
 | |
|         ...prev,
 | |
|         [siteId]: [...(prev[siteId] ?? []), id],
 | |
|       }));
 | |
|       setShowModal(false);
 | |
|       setDeviceId("");
 | |
|     } catch (error) {
 | |
|       console.error("Failed to start logging:", error);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const handleStopLogging = async () => {
 | |
|     try {
 | |
|       // Stop only this site's topics (both function types for each device)
 | |
|       const topics = (loggedDevices[siteId] ?? []).flatMap(did => [
 | |
|         `ADW300/${siteId}/${did}/grid`,
 | |
|         `ADW300/${siteId}/${did}/solar`,
 | |
|       ]);
 | |
|       await axios.post(`${API_URL}/stop-logging`, topics.length ? { topics } : {});
 | |
| 
 | |
|       setLoggedDevices(prev => ({ ...prev, [siteId]: [] }));
 | |
|       console.log("Stopped logging for", siteId);
 | |
|     } catch (error) {
 | |
|       console.error("Failed to stop logging:", error);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const statusClass = useMemo(() => {
 | |
|     const s = (status ?? "").toLowerCase();
 | |
|     if (s === "open" || s === "active") return "text-green-500";
 | |
|     if (s === "completed" || s === "closed") return "text-blue-500";
 | |
|     if (s === "inactive" || s === "on hold") return "text-orange-500";
 | |
|     if (s === "faulty" || s === "cancelled") return "text-red-500";
 | |
|     return "text-gray-500";
 | |
|   }, [status]);
 | |
| 
 | |
|   return (
 | |
|     <div className="bg-white p-4 rounded-lg shadow-md space-y-2 dark:bg-rtgray-800 dark:text-white-light">
 | |
|       <h2 className="text-xl font-semibold mb-3">Site Details</h2>
 | |
| 
 | |
|       {/* Status (from CRM) */}
 | |
|       <div className="flex justify-between items-center text-base">
 | |
|         <p className="text-gray-600 dark:text-white/85 font-medium">Status:</p>
 | |
|         <p className={`font-semibold ${statusClass}`}>{status ?? "—"}</p>
 | |
|       </div>
 | |
| 
 | |
|       {/* Site ID */}
 | |
|       <div className="flex justify-between items-center text-base">
 | |
|         <p className="text-gray-600 dark:text-white/85 font-medium">Site ID:</p>
 | |
|         <p className="font-medium">{siteId}</p>
 | |
|       </div>
 | |
| 
 | |
|       {/* Location */}
 | |
|       <div className="flex justify-between items-center text-base">
 | |
|         <p className="text-gray-600 dark:text-white/85 font-medium">Location:</p>
 | |
|         <p className="font-medium">{location}</p>
 | |
|       </div>
 | |
| 
 | |
|       {/* Inverter Provider */}
 | |
|       <div className="flex justify-between items-center text-base">
 | |
|         <p className="text-gray-600 dark:text-white/85 font-medium">Inverter Provider:</p>
 | |
|         <p className="font-medium">{inverterProvider}</p>
 | |
|       </div>
 | |
| 
 | |
|       {/* Emergency Contact */}
 | |
|       <div className="flex justify-between items-center text-base">
 | |
|         <p className="text-gray-600 dark:text-white/85 font-medium">Emergency Contact:</p>
 | |
|         <p className="font-medium">{emergencyContact}</p>
 | |
|       </div>
 | |
| 
 | |
|       {/* Last Sync */}
 | |
|       <div className="flex justify-between items-center text-base">
 | |
|         <p className="text-gray-600 dark:text-white/85 font-medium">Last Sync:</p>
 | |
|         <p className="font-medium">{lastSyncTimestamp}</p>
 | |
|       </div>
 | |
|     </div>
 | |
|   );
 | |
| };
 | |
| 
 | |
| export default SiteStatus;
 | |
| 
 |