Compare commits
6 Commits
bfd091b1b1
...
4a42c428f0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a42c428f0 | ||
|
|
1d3d04d49c | ||
|
|
dd9980409c | ||
|
|
ea6d71a4f5 | ||
|
|
13ca1cece0 | ||
|
|
f22bb4b232 |
@@ -23,4 +23,4 @@ NEXT_PUBLIC_USE_MOCKS=true
|
|||||||
# z.B. http://10.10.0.13/xyz/index.aspx -> basePath in config.json auf /xyz setzen
|
# z.B. http://10.10.0.13/xyz/index.aspx -> basePath in config.json auf /xyz setzen
|
||||||
# basePath wird jetzt in public/config.json gepflegt
|
# basePath wird jetzt in public/config.json gepflegt
|
||||||
# App-Versionsnummer
|
# App-Versionsnummer
|
||||||
NEXT_PUBLIC_APP_VERSION=1.1.382
|
NEXT_PUBLIC_APP_VERSION=1.1.388
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ NEXT_PUBLIC_USE_MOCKS=false
|
|||||||
# basePath wird jetzt in public/config.json gepflegt
|
# basePath wird jetzt in public/config.json gepflegt
|
||||||
|
|
||||||
# App-Versionsnummer
|
# App-Versionsnummer
|
||||||
NEXT_PUBLIC_APP_VERSION=1.1.382
|
NEXT_PUBLIC_APP_VERSION=1.1.388
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { useMapComponentState } from "@/components/hooks/useMapComponentState.js
|
|||||||
import CoordinatePopup from "@/components/contextmenu/CoordinatePopup.js";
|
import CoordinatePopup from "@/components/contextmenu/CoordinatePopup.js";
|
||||||
//----------Ui Widgets----------------
|
//----------Ui Widgets----------------
|
||||||
import MapLayersControlPanel from "@/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js";
|
import MapLayersControlPanel from "@/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js";
|
||||||
|
import AlarmIndicator from "@/components/uiWidgets/AlarmIndicator";
|
||||||
import CoordinateInput from "@/components/uiWidgets/CoordinateInput.js";
|
import CoordinateInput from "@/components/uiWidgets/CoordinateInput.js";
|
||||||
import VersionInfoModal from "@/components/uiWidgets/VersionInfoModal.js";
|
import VersionInfoModal from "@/components/uiWidgets/VersionInfoModal.js";
|
||||||
import AreaDropdown from "@/components/uiWidgets/AreaDropdown";
|
import AreaDropdown from "@/components/uiWidgets/AreaDropdown";
|
||||||
@@ -132,7 +133,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
|||||||
const poiLayerVisible = useSelector(state => state.poiLayerVisible.visible);
|
const poiLayerVisible = useSelector(state => state.poiLayerVisible.visible);
|
||||||
const zoomTrigger = useSelector(state => state.zoomTrigger.trigger);
|
const zoomTrigger = useSelector(state => state.zoomTrigger.trigger);
|
||||||
const poiReadTrigger = useSelector(state => state.poiReadFromDbTrigger.trigger);
|
const poiReadTrigger = useSelector(state => state.poiReadFromDbTrigger.trigger);
|
||||||
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict);
|
// entfernt, da weiter unten dynamisch und mit Fallback deklariert
|
||||||
const gisSystemStaticStatus = useSelector(state => state.gisSystemStatic.status);
|
const gisSystemStaticStatus = useSelector(state => state.gisSystemStatic.status);
|
||||||
const polylineEventsDisabled = useSelector(state => state.polylineEventsDisabled.disabled);
|
const polylineEventsDisabled = useSelector(state => state.polylineEventsDisabled.disabled);
|
||||||
const mapLayersVisibility = useSelector(selectMapLayersState) || {};
|
const mapLayersVisibility = useSelector(selectMapLayersState) || {};
|
||||||
@@ -143,12 +144,37 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
|||||||
selectGisLinesStatusFromWebservice
|
selectGisLinesStatusFromWebservice
|
||||||
);
|
);
|
||||||
|
|
||||||
// Alarm Status aus GisStationsStatusDistrict
|
// Alarm Status und Link dynamisch aus GisStationsStaticDistrict
|
||||||
const gisStationsStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.data);
|
const gisStationsStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.data);
|
||||||
// Unterstützt sowohl Array-Shape (Statis[]) als auch Objekt mit Statis-Array
|
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict) || {};
|
||||||
const hasActiveAlarm = Array.isArray(gisStationsStatusDistrict)
|
const pointsArr = GisStationsStaticDistrict.Points || [];
|
||||||
? gisStationsStatusDistrict.some(item => item?.Alarm === 1)
|
let hasActiveAlarm = false;
|
||||||
: gisStationsStatusDistrict?.Statis?.some(item => item?.Alarm === 1) || false;
|
let alarmLink = "";
|
||||||
|
let alarmText = "";
|
||||||
|
let alarmIdLD = null;
|
||||||
|
if (Array.isArray(gisStationsStatusDistrict)) {
|
||||||
|
const alarmObj = gisStationsStatusDistrict.find(item => item?.Alarm === 1);
|
||||||
|
hasActiveAlarm = !!alarmObj;
|
||||||
|
alarmIdLD = alarmObj?.IdLD;
|
||||||
|
alarmText = alarmObj?.Me || "Alarm aktiv";
|
||||||
|
} else if (gisStationsStatusDistrict?.Statis) {
|
||||||
|
const alarmObj = gisStationsStatusDistrict.Statis.find(item => item?.Alarm === 1);
|
||||||
|
hasActiveAlarm = !!alarmObj;
|
||||||
|
alarmIdLD = alarmObj?.IdLD;
|
||||||
|
alarmText = alarmObj?.Me || "Alarm aktiv";
|
||||||
|
}
|
||||||
|
if (hasActiveAlarm && alarmIdLD) {
|
||||||
|
const staticObj = pointsArr.find(p => p.IdLD === alarmIdLD);
|
||||||
|
if (staticObj && staticObj.Link) {
|
||||||
|
// Link kann relativ sein, ggf. mit Host ergänzen
|
||||||
|
const isAbsolute =
|
||||||
|
staticObj.Link.startsWith("http://") || staticObj.Link.startsWith("https://");
|
||||||
|
alarmLink = isAbsolute
|
||||||
|
? staticObj.Link
|
||||||
|
: // : `${window.location.origin}/talas5/devices/${staticObj.Link}`;
|
||||||
|
`http://10.10.0.13/talas5/devices/${staticObj.Link}`; // nur zum Testen
|
||||||
|
}
|
||||||
|
}
|
||||||
const poiIconsData = useSelector(selectPoiIconsData);
|
const poiIconsData = useSelector(selectPoiIconsData);
|
||||||
const poiIconsStatus = useSelector(selectPoiIconsStatus);
|
const poiIconsStatus = useSelector(selectPoiIconsStatus);
|
||||||
const poiTypData = useSelector(selectPoiTypData);
|
const poiTypData = useSelector(selectPoiTypData);
|
||||||
@@ -1185,17 +1211,8 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
|||||||
<div id="map" ref={mapRef} className="z-0" style={{ height: "100vh", width: "100vw" }}></div>
|
<div id="map" ref={mapRef} className="z-0" style={{ height: "100vh", width: "100vw" }}></div>
|
||||||
{/* Top-right controls: layers, info, expand, edit, and base map stack */}
|
{/* Top-right controls: layers, info, expand, edit, and base map stack */}
|
||||||
<div className="absolute top-3 right-3 z-50 pointer-events-auto flex items-center gap-2">
|
<div className="absolute top-3 right-3 z-50 pointer-events-auto flex items-center gap-2">
|
||||||
{/* Alarm-Icon - nur anzeigen wenn Alarm aktiv */}
|
{/* Alarm-Icon - nur anzeigen wenn Alarm aktiv und Link vorhanden */}
|
||||||
{hasActiveAlarm && (
|
<AlarmIndicator hasAlarm={hasActiveAlarm} alarmLink={alarmLink} alarmText={alarmText} />
|
||||||
<button
|
|
||||||
onClick={() => {}}
|
|
||||||
aria-label="Alarm aktiv"
|
|
||||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
|
||||||
title="Alarm aktiv"
|
|
||||||
>
|
|
||||||
<AlarmIcon className="h-8 w-8 animate-pulse text-red-500" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{/* Marker-Icon (line-md) */}
|
{/* Marker-Icon (line-md) */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setOverlay(prev => (prev === "area" ? null : "area"))}
|
onClick={() => setOverlay(prev => (prev === "area" ? null : "area"))}
|
||||||
|
|||||||
53
components/uiWidgets/AlarmIndicator.js
Normal file
53
components/uiWidgets/AlarmIndicator.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AlarmIcon from "@/components/icons/material-symbols/AlarmIcon";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import styles from "./AlarmIndicator.module.css";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlarmIndicator zeigt ein Alarm-Icon, das bei Klick den AlarmLink in neuem Tab öffnet.
|
||||||
|
* @param {boolean} hasAlarm - Ob ein Alarm aktiv ist
|
||||||
|
* @param {string} alarmLink - Link zur Alarm-Detailseite
|
||||||
|
* @param {string} [alarmText] - Optionaler Tooltip-Text
|
||||||
|
* @param {string} [animation] - "shake" | "rotate" | "blink" | "pulse" (default: "shake")
|
||||||
|
* @param {number} [pulseDuration] - Animationsdauer in Sekunden (default: 0.5)
|
||||||
|
*/
|
||||||
|
const AlarmIndicator = ({
|
||||||
|
hasAlarm,
|
||||||
|
alarmLink,
|
||||||
|
alarmText,
|
||||||
|
animation = "pulse",
|
||||||
|
pulseDuration = 0.5, // default: 1
|
||||||
|
}) => {
|
||||||
|
if (!hasAlarm || !alarmLink) return null;
|
||||||
|
// Animation-Klasse wählen
|
||||||
|
let animationClass = styles.fastPulse;
|
||||||
|
let style = { animationDuration: `${pulseDuration}s` };
|
||||||
|
if (animation === "shake") {
|
||||||
|
animationClass = styles.shakeAlarm;
|
||||||
|
} else if (animation === "rotate") {
|
||||||
|
animationClass = styles.rotateAlarm;
|
||||||
|
} else if (animation === "blink") {
|
||||||
|
animationClass = styles.blinkAlarm;
|
||||||
|
} else if (animation === "pulse") {
|
||||||
|
animationClass = styles.fastPulse;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip title={alarmText || "Alarm aktiv"}>
|
||||||
|
<span
|
||||||
|
style={{ cursor: "pointer", color: "red" }}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.open(alarmLink, "_blank");
|
||||||
|
}}
|
||||||
|
aria-label="Alarm aktiv"
|
||||||
|
>
|
||||||
|
<AlarmIcon
|
||||||
|
className={`h-14 w-14 mr-6 ${animationClass} text-red-800 bg-red-300`}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlarmIndicator;
|
||||||
62
components/uiWidgets/AlarmIndicator.module.css
Normal file
62
components/uiWidgets/AlarmIndicator.module.css
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
.fastPulse {
|
||||||
|
animation: fast-pulse 0.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fast-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shakeAlarm {
|
||||||
|
animation: shake-alarm 0.5s infinite cubic-bezier(0.36, 0.07, 0.19, 0.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake-alarm {
|
||||||
|
10%,
|
||||||
|
90% {
|
||||||
|
transform: translateX(-1px);
|
||||||
|
}
|
||||||
|
20%,
|
||||||
|
80% {
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
30%,
|
||||||
|
50%,
|
||||||
|
70% {
|
||||||
|
transform: translateX(-4px);
|
||||||
|
}
|
||||||
|
40%,
|
||||||
|
60% {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rotateAlarm {
|
||||||
|
animation: rotate-alarm 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate-alarm {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blinkAlarm {
|
||||||
|
animation: blink-alarm 0.7s steps(2, start) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-alarm {
|
||||||
|
to {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
components/uiWidgets/alarm-indicator-fastpulse.css
Normal file
15
components/uiWidgets/alarm-indicator-fastpulse.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@keyframes fast-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fast-pulse {
|
||||||
|
animation: fast-pulse 0.5s infinite;
|
||||||
|
}
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "nodemap",
|
"name": "nodemap",
|
||||||
"version": "1.1.382",
|
"version": "1.1.388",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "nodemap",
|
"name": "nodemap",
|
||||||
"version": "1.1.382",
|
"version": "1.1.388",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nodemap",
|
"name": "nodemap",
|
||||||
"version": "1.1.382",
|
"version": "1.1.388",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ async function selectStation(page, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("MapComponent", async ({ page }) => {
|
test("MapComponent", async ({ page }) => {
|
||||||
|
// Login auf 13.er TALAS
|
||||||
|
await page.goto("http://10.10.0.13/talas5/login.aspx");
|
||||||
|
await page.locator("#m_textboxUserName_I").click();
|
||||||
|
await page.locator("#m_textboxUserName_I").fill("admin");
|
||||||
|
await page.locator("#m_textboxUserName_I").press("Tab");
|
||||||
|
await page.locator("#m_textboxPassword_I").fill("admin");
|
||||||
|
await page.getByRole("cell", { name: "Anmelden Anmelden" }).locator("span").click();
|
||||||
|
|
||||||
// Set initial localStorage BEFORE navigation so the app reads them on load
|
// Set initial localStorage BEFORE navigation so the app reads them on load
|
||||||
await page.addInitScript(() => {
|
await page.addInitScript(() => {
|
||||||
localStorage.setItem("editMode", "false");
|
localStorage.setItem("editMode", "false");
|
||||||
@@ -98,7 +106,17 @@ test("MapComponent", async ({ page }) => {
|
|||||||
|
|
||||||
// 7) Marker setzen und Stationen wählen
|
// 7) Marker setzen und Stationen wählen
|
||||||
await page.getByLabel("Marker").click();
|
await page.getByLabel("Marker").click();
|
||||||
await expect(page.getByText("Station wählenBitte wählen…")).toBeVisible();
|
// ...existing code...
|
||||||
|
// ...existing code...
|
||||||
|
await expect(page.getByText("Station wählen")).toBeVisible();
|
||||||
|
const select = page.locator("select");
|
||||||
|
await expect(select).toBeVisible();
|
||||||
|
await expect(select).toBeEnabled();
|
||||||
|
// Prüfe, ob die gewünschten Optionen existieren (attached)
|
||||||
|
await expect(select.locator('option[value="50977"]')).toBeAttached();
|
||||||
|
await expect(select.locator('option[value="50986"]')).toBeAttached();
|
||||||
|
|
||||||
|
// ----------------------------------------------
|
||||||
await selectStation(page, "50977");
|
await selectStation(page, "50977");
|
||||||
await page.getByLabel("Marker").click();
|
await page.getByLabel("Marker").click();
|
||||||
await selectStation(page, "50986");
|
await selectStation(page, "50986");
|
||||||
@@ -112,6 +130,25 @@ test("MapComponent", async ({ page }) => {
|
|||||||
//wait 3 seconds
|
//wait 3 seconds
|
||||||
// plusIcon
|
// plusIcon
|
||||||
await page.getByTestId("zoom-in").click(); //plus
|
await page.getByTestId("zoom-in").click(); //plus
|
||||||
|
//--------------------------------------------
|
||||||
|
// Prüfe Alarm-Icon
|
||||||
|
|
||||||
|
await page.goto("http://10.10.0.13/talas5/login.aspx");
|
||||||
|
await page.locator("#m_textboxUserName_I").click();
|
||||||
|
await page.locator("#m_textboxUserName_I").fill("admin");
|
||||||
|
await page.locator("#m_textboxUserName_I").press("Tab");
|
||||||
|
await page.locator("#m_textboxPassword_I").fill("admin");
|
||||||
|
await page.getByRole("cell", { name: "Anmelden Anmelden" }).locator("span").click();
|
||||||
|
console.log("Login auf 13.er TALAS erfolgreich");
|
||||||
|
//warte 10 Sekunden
|
||||||
|
await page.waitForTimeout(10000);
|
||||||
|
await page.goto("http://localhost:3000/?m=12&u=484");
|
||||||
|
const page1Promise = page.waitForEvent("popup");
|
||||||
|
await page.getByLabel("Alarm aktiv").locator("path").click();
|
||||||
|
const page1 = await page1Promise;
|
||||||
|
await expect(
|
||||||
|
page1.getByText("Standort Rastede > Bereich Littwin > TALAS CPL V3.5", { exact: true })
|
||||||
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Powershell Befehl ->das führt langsam aus mit 1 Sekunde Pause zwischen den Aktionen
|
/* Powershell Befehl ->das führt langsam aus mit 1 Sekunde Pause zwischen den Aktionen
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"LD_Name": "GMA-isa-test",
|
"LD_Name": "GMA-isa-test",
|
||||||
"IdLD": 50981,
|
"IdLD": 50922,
|
||||||
"Device": "Glättemeldeanlage",
|
"Device": "Glättemeldeanlage",
|
||||||
"Link": "gma.aspx?ver=1&id=50981",
|
"Link": "gma.aspx?ver=1&id=50981",
|
||||||
"Location_Name": "Littwin",
|
"Location_Name": "Littwin",
|
||||||
|
|||||||
@@ -317,7 +317,7 @@
|
|||||||
"Me": "KÜG 06: Aderbruch kommend",
|
"Me": "KÜG 06: Aderbruch kommend",
|
||||||
"Feld": 4,
|
"Feld": 4,
|
||||||
"Icon": 0,
|
"Icon": 0,
|
||||||
"Alarm": 0
|
"Alarm": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"IdLD": 50977,
|
"IdLD": 50977,
|
||||||
|
|||||||
Reference in New Issue
Block a user