All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m56s
152 lines
4.9 KiB
TypeScript
152 lines
4.9 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_FASTAPI_URL;
|
|
|
|
const SiteStatus = ({
|
|
selectedSite,
|
|
siteId,
|
|
status,
|
|
location,
|
|
inverterProvider,
|
|
emergencyContact,
|
|
lastSyncTimestamp,
|
|
}: SiteStatusProps) => {
|
|
|
|
// --- WebSocket to receive MQTT-forwarded messages ---
|
|
useEffect(() => {
|
|
const ws = new WebSocket(`${API_URL}/ws`);
|
|
|
|
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;
|
|
|