From 401a89dd7a4de937219a66fff1b99849d4cf6707 Mon Sep 17 00:00:00 2001 From: Syasya Date: Wed, 13 Aug 2025 12:31:23 +0800 Subject: [PATCH] crm integration 2 --- components/dashboards/SiteSelector.tsx | 47 +++- components/dashboards/SiteStatus.tsx | 360 ++++++++++++------------- 2 files changed, 214 insertions(+), 193 deletions(-) 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 ( -
- +
+ +
); }; 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 */} +
+

Site ID:

+

{siteId}

+
- 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 ? ( + + ) : ( + + )} +
+ + {/* Modal */} + {showModal && ( +
+
+

Enter Device Info

+ + setDeviceId(e.target.value)} + /> + + + +
+ +
- - {/* Site ID */} -
-

Site ID:

-

{siteId}

-
- - {/* Location */} -
-

Location:

-

{location}

-
- - {/* Inverter Provider */} -
-

Inverter Provider:

-

{inverterProvider}

-
- - {/* Emergency Contact */} -
-

Emergency Contact:

-

{emergencyContact}

-
- - {/* Last Sync */} -
-

Last Sync:

-

{lastSyncTimestamp}

-
- - {/* Start Logging Button */} -
- {devicesAtSite.length > 0 ? ( - - ) : ( - - )} -
- - - {/* Modal */} - {showModal && ( -
-
-

Enter Device Info

- - setDeviceId(e.target.value)} - /> - - - -
- - -
-
-
- )} +
- ); + )} +
+ ); }; export default SiteStatus; +