diff --git a/components/dashboards/SiteSelector.tsx b/components/dashboards/SiteSelector.tsx
index a8f0544..616bdc8 100644
--- a/components/dashboards/SiteSelector.tsx
+++ b/components/dashboards/SiteSelector.tsx
@@ -1,26 +1,51 @@
+'use client';
-import type { SiteName } from '@/components/dashboards/SiteStatus';
+type Option = { label: string; value: string };
type SiteSelectorProps = {
- selectedSite: SiteName;
- setSelectedSite: (site: SiteName) => void;
+ options: Option[]; // e.g. [{label: 'Timo… (Installation)', value: 'PROJ-0008'}, …]
+ selectedValue: string | null; // the selected project "name" (siteId) or null
+ onChange: (value: string) => void; // called with the selected value
+ label?: string;
+ disabled?: boolean;
};
-const SiteSelector = ({ selectedSite, setSelectedSite }: SiteSelectorProps) => {
+
+const SiteSelector = ({
+ options,
+ selectedValue,
+ onChange,
+ label = 'Select Site:',
+ disabled = false,
+}: SiteSelectorProps) => {
+ const isEmpty = !options || options.length === 0;
+
return (
-
-
Select Site:
+
+
+ {label}
+
+
setSelectedSite(e.target.value as SiteName)}
+ value={selectedValue ?? ''} // keep controlled even when null
+ onChange={(e) => onChange(e.target.value)}
+ disabled={disabled || isEmpty}
>
- Site A
- Site B
- Site C
+ {/* Placeholder when nothing selected */}
+
+ {isEmpty ? 'No sites available' : 'Choose a site…'}
+
+
+ {options.map((opt) => (
+
+ {opt.label}
+
+ ))}
);
};
export default SiteSelector;
+
diff --git a/components/dashboards/SiteStatus.tsx b/components/dashboards/SiteStatus.tsx
index fb252a6..50aa9df 100644
--- a/components/dashboards/SiteStatus.tsx
+++ b/components/dashboards/SiteStatus.tsx
@@ -1,217 +1,213 @@
-import axios from "axios";
-import React, { useState, useEffect } from "react";
+'use client';
-export type SiteName = 'Site A' | 'Site B' | 'Site C';
+import axios from "axios";
+import React, { useState, useEffect, useMemo } from "react";
+
+export type SiteName = string;
interface SiteStatusProps {
- selectedSite: SiteName;
- location: string;
- inverterProvider: string;
- emergencyContact: string;
- lastSyncTimestamp: string;
+ 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,
- location,
- inverterProvider,
- emergencyContact,
- lastSyncTimestamp,
+ selectedSite,
+ siteId,
+ status,
+ location,
+ inverterProvider,
+ emergencyContact,
+ lastSyncTimestamp,
}: SiteStatusProps) => {
- useEffect(() => {
- const ws = new WebSocket("ws://localhost:8000/ws");
-
- ws.onmessage = (event) => {
- const data = event.data;
- alert(`MQTT: ${data}`);
- };
+ // --- 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
>({});
+ const devicesAtSite = loggedDevices[siteId] ?? [];
- const [showModal, setShowModal] = useState(false);
- const [deviceId, setDeviceId] = useState("");
- const [functionType, setFunctionType] = useState("Grid");
+ const handleStartLogging = () => setShowModal(true);
- // Map site names to site IDs
- const siteIdMap: Record = {
- "Site A": "site_01",
- "Site B": "site_02",
- "Site C": "site_03",
- };
+ const handleConfirm = async () => {
+ const id = deviceId.trim();
+ if (!id) return;
- // Track devices connected per site
- const [loggedDevices, setLoggedDevices] = useState>({
- site_01: [],
- site_02: [],
- site_03: [],
- });
+ const topic = `ADW300/${siteId}/${id}/${functionType.toLowerCase()}`;
- const siteId = siteIdMap[selectedSite];
- const devicesAtSite = loggedDevices[siteId] || [];
+ try {
+ const response = await axios.post(`${API_URL}/start-logging`, { topics: [topic] });
+ console.log("Started logging:", response.data);
- const handleStartLogging = () => {
- setShowModal(true);
- };
+ setLoggedDevices(prev => ({
+ ...prev,
+ [siteId]: [...(prev[siteId] ?? []), id],
+ }));
+ setShowModal(false);
+ setDeviceId("");
+ } catch (error) {
+ console.error("Failed to start logging:", error);
+ }
+ };
- const handleConfirm = async () => {
- const siteId = siteIdMap[selectedSite];
- const topic = `ADW300/${siteId}/${deviceId}/${functionType.toLowerCase()}`;
+ 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 } : {});
- try {
- const response = await axios.post("http://localhost:8000/start-logging", {
- topics: [topic],
- });
- console.log("Started logging:", response.data);
+ setLoggedDevices(prev => ({ ...prev, [siteId]: [] }));
+ console.log("Stopped logging for", siteId);
+ } catch (error) {
+ console.error("Failed to stop logging:", error);
+ }
+ };
- // Add device to list
- setLoggedDevices((prev) => ({
- ...prev,
- [siteId]: [...(prev[siteId] || []), deviceId],
- }));
- setShowModal(false);
+ 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]);
- } catch (error) {
- console.error("Failed to start logging:", error);
- }
- };
+ return (
+
+
Site Details
- const handleStopLogging = async () => {
- try {
- await axios.post("http://localhost:8000/stop-logging");
+ {/* Status (from CRM) */}
+
+
Status:
+
{status ?? "—"}
+
- // Clear all devices for the site (or modify to remove only specific one)
- setLoggedDevices((prev) => ({
- ...prev,
- [siteId]: [],
- }));
+ {/* Site ID */}
+
- console.log("Stopped logging for", siteId);
- } catch (error) {
- console.error("Failed to stop logging:", error);
- }
- };
+ {/* Location */}
+
+
Location:
+
{location}
+
- const statusMap: Record
= {
- 'Site A': 'Active',
- 'Site B': 'Inactive',
- 'Site C': 'Faulty',
- };
+ {/* Inverter Provider */}
+
+
Inverter Provider:
+
{inverterProvider}
+
- return (
-
-
Site Details
+ {/* Emergency Contact */}
+
+
Emergency Contact:
+
{emergencyContact}
+
- {/* Status */}
-
-
Status:
-
- {statusMap[selectedSite]}
-
+ {/* Last Sync */}
+
+
Last Sync:
+
{lastSyncTimestamp}
+
+
+ {/* Start/Stop */}
+
+ {devicesAtSite.length > 0 ? (
+
+ Stop Logging
+
+ ) : (
+
+ Start Logging
+
+ )}
+
+
+ {/* Modal */}
+ {showModal && (
+
+
+
Enter Device Info
+
+
setDeviceId(e.target.value)}
+ />
+
+
setFunctionType(e.target.value as "Grid" | "Solar")}
+ >
+ Grid
+ Solar
+
+
+
+ setShowModal(false)}
+ className="btn-primary bg-white border-2 border-black hover:bg-rtgray-200 px-4 py-2"
+ >
+ Cancel
+
+
+ Confirm
+
-
- {/* Site ID */}
-
-
- {/* Location */}
-
-
Location:
-
{location}
-
-
- {/* Inverter Provider */}
-
-
Inverter Provider:
-
{inverterProvider}
-
-
- {/* Emergency Contact */}
-
-
Emergency Contact:
-
{emergencyContact}
-
-
- {/* Last Sync */}
-
-
Last Sync:
-
{lastSyncTimestamp}
-
-
- {/* Start Logging Button */}
-
- {devicesAtSite.length > 0 ? (
-
- Stop Logging
-
- ) : (
-
- Start Logging
-
- )}
-
-
-
- {/* Modal */}
- {showModal && (
-
-
-
Enter Device Info
-
-
setDeviceId(e.target.value)}
- />
-
-
setFunctionType(e.target.value)}
- >
- Grid
- Solar
-
-
-
- setShowModal(false)}
- className="btn-primary bg-white border-2 border-black hover:bg-rtgray-200 px-4 py-2"
- >
- Cancel
-
-
- Confirm
-
-
-
-
- )}
+
- );
+ )}
+
+ );
};
export default SiteStatus;
+