Compare commits
21 Commits
3a9b436352
...
bfd091b1b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfd091b1b1 | ||
|
|
81b6379895 | ||
|
|
42ca88d27e | ||
|
|
fdb70d892c | ||
|
|
73e9c63e36 | ||
|
|
e520207526 | ||
|
|
2e5acf9327 | ||
|
|
cdfdd3d6cf | ||
|
|
5b86d5293b | ||
|
|
31c770f778 | ||
|
|
051dd4c306 | ||
|
|
995f084e15 | ||
|
|
eaacec71da | ||
|
|
6bc2e16657 | ||
|
|
1208024f76 | ||
|
|
369f29a769 | ||
|
|
d166b2468d | ||
|
|
59c8680c23 | ||
|
|
1a046f8212 | ||
|
|
e35216daf5 | ||
|
|
91ad47166f |
@@ -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
|
||||
# basePath wird jetzt in public/config.json gepflegt
|
||||
# App-Versionsnummer
|
||||
NEXT_PUBLIC_APP_VERSION=1.1.361
|
||||
NEXT_PUBLIC_APP_VERSION=1.1.382
|
||||
|
||||
@@ -24,4 +24,4 @@ NEXT_PUBLIC_USE_MOCKS=false
|
||||
# basePath wird jetzt in public/config.json gepflegt
|
||||
|
||||
# App-Versionsnummer
|
||||
NEXT_PUBLIC_APP_VERSION=1.1.361
|
||||
NEXT_PUBLIC_APP_VERSION=1.1.382
|
||||
|
||||
22
.gitignore
vendored
@@ -35,3 +35,25 @@ docs.zip
|
||||
/mockData/
|
||||
/__mocks__/
|
||||
/__tests__/
|
||||
|
||||
# --- Playwright artifacts & test selection ---
|
||||
# Ignore Playwright output folders nested under playwright/
|
||||
/playwright/test-results/
|
||||
/playwright/playwright-report/
|
||||
/playwright/.last-run.json
|
||||
# If you ever enable these paths, keep them under playwright/ and ignore them
|
||||
/playwright/traces/
|
||||
/playwright/screenshots/
|
||||
/playwright/videos/
|
||||
# Ignore JUnit report artifacts under playwright/ (currently unused)
|
||||
/playwright/reports/junit/
|
||||
|
||||
# Track only spec files under playwright/tests; ignore other files in that folder
|
||||
/playwright/tests/**
|
||||
!/playwright/tests/**/*.spec.js
|
||||
!/playwright/tests/**/*.spec.ts
|
||||
|
||||
# Ignore Playwright cache if present
|
||||
/playwright/.cache/
|
||||
# playwright reports
|
||||
/playwright/reports/
|
||||
|
||||
18
components/icons/material-symbols/AlarmIcon.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const AlarmIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="red"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 6.9L3.87 4.78l1.41-1.41L7.4 5.5zM13 1v3h-2V1zm7.13 3.78L18 6.9l-1.4-1.4l2.12-2.13zM4.5 10.5v2h-3v-2zm15 0h3v2h-3zM6 20h12a2 2 0 0 1 2 2H4a2 2 0 0 1 2-2m6-15a6 6 0 0 1 6 6v8H6v-8a6 6 0 0 1 6-6"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default AlarmIcon;
|
||||
18
components/icons/material-symbols/EditIcon.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const EditIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default EditIcon;
|
||||
18
components/icons/material-symbols/EditOffIcon.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const EditOffIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18 21l-3-3m-12.728-.364A9 9 0 015.636 5.636m0 0L3 3l3 3m9.364 9.364L18 21M5.636 5.636L3 3"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default EditOffIcon;
|
||||
18
components/icons/material-symbols/ExpandIcon.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const ExpandIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default ExpandIcon;
|
||||
18
components/icons/material-symbols/InfoIcon.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const InfoIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default InfoIcon;
|
||||
19
components/icons/material-symbols/MapMarkerIcon.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const MapMarkerIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="rgb(0, 174, 239)"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25s-7.5-4.108-7.5-11.25a7.5 7.5 0 1115 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default MapMarkerIcon;
|
||||
18
components/icons/material-symbols/MenuIcon.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const MenuIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default MenuIcon;
|
||||
14
components/icons/material-symbols/MinusIcon.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const MinusIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 12h14" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default MinusIcon;
|
||||
14
components/icons/material-symbols/PlusIcon.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const PlusIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default PlusIcon;
|
||||
18
components/icons/material-symbols/SearchIcon.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const SearchIcon = ({ className = "h-8 w-8" }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="rgb(0, 174, 239)"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default SearchIcon;
|
||||
@@ -7,6 +7,16 @@ import "leaflet-contextmenu";
|
||||
import "leaflet.smooth_marker_bouncing";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { Icon } from "@iconify/react";
|
||||
import EditIcon from "@/components/icons/material-symbols/EditIcon";
|
||||
import EditOffIcon from "@/components/icons/material-symbols/EditOffIcon";
|
||||
import SearchIcon from "@/components/icons/material-symbols/SearchIcon";
|
||||
import MenuIcon from "@/components/icons/material-symbols/MenuIcon";
|
||||
import InfoIcon from "@/components/icons/material-symbols/InfoIcon";
|
||||
import AlarmIcon from "@/components/icons/material-symbols/AlarmIcon";
|
||||
import MapMarkerIcon from "@/components/icons/material-symbols/MapMarkerIcon";
|
||||
import ExpandIcon from "@/components/icons/material-symbols/ExpandIcon";
|
||||
import PlusIcon from "@/components/icons/material-symbols/PlusIcon";
|
||||
import MinusIcon from "@/components/icons/material-symbols/MinusIcon";
|
||||
import PoiUpdateModal from "@/components/pois/poiUpdateModal/PoiUpdateModal.js";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import plusRoundIcon from "../icons/devices/overlapping/PlusRoundIcon.js";
|
||||
@@ -24,9 +34,9 @@ import { useMapComponentState } from "@/components/hooks/useMapComponentState.js
|
||||
import CoordinatePopup from "@/components/contextmenu/CoordinatePopup.js";
|
||||
//----------Ui Widgets----------------
|
||||
import MapLayersControlPanel from "@/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js";
|
||||
import BaseMapPanel from "@/components/uiWidgets/baseMapPanel/BaseMapPanel.js";
|
||||
import CoordinateInput from "@/components/uiWidgets/CoordinateInput.js";
|
||||
import VersionInfoModal from "@/components/uiWidgets/VersionInfoModal.js";
|
||||
import AreaDropdown from "@/components/uiWidgets/AreaDropdown";
|
||||
//----------Daten aus API--------------------
|
||||
import { fetchPoiDataService } from "@/services/database/pois/fetchPoiDataByIdService.js";
|
||||
import AddPOIModal from "@/components/pois/AddPOIModal.js";
|
||||
@@ -132,6 +142,13 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
const { data: gisLinesStatusData, status: statusGisLinesStatus } = useSelector(
|
||||
selectGisLinesStatusFromWebservice
|
||||
);
|
||||
|
||||
// Alarm Status aus GisStationsStatusDistrict
|
||||
const gisStationsStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.data);
|
||||
// Unterstützt sowohl Array-Shape (Statis[]) als auch Objekt mit Statis-Array
|
||||
const hasActiveAlarm = Array.isArray(gisStationsStatusDistrict)
|
||||
? gisStationsStatusDistrict.some(item => item?.Alarm === 1)
|
||||
: gisStationsStatusDistrict?.Statis?.some(item => item?.Alarm === 1) || false;
|
||||
const poiIconsData = useSelector(selectPoiIconsData);
|
||||
const poiIconsStatus = useSelector(selectPoiIconsStatus);
|
||||
const poiTypData = useSelector(selectPoiTypData);
|
||||
@@ -150,6 +167,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
const [showVersionInfoModal, setShowVersionInfoModal] = useState(false);
|
||||
const [poiTypMap, setPoiTypMap] = useState(new Map());
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
const [showAreaDropdown, setShowAreaDropdown] = useState(() => {
|
||||
try {
|
||||
const v = localStorage.getItem("showAreaDropdown");
|
||||
return v === null ? false : v === "true";
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const poiLayerRef = useRef(null); // Referenz auf die Layer-Gruppe für Datenbank-Marker
|
||||
const mapRef = useRef(null); // Referenz auf das DIV-Element der Karte
|
||||
const [map, setMap] = useState(null); // Zustand der Karteninstanz
|
||||
@@ -172,15 +197,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
// Sichtbarkeit des Base-Map Panels (oben rechts, unter Toolbar)
|
||||
const [showBaseMapPanel, setShowBaseMapPanel] = useState(() => {
|
||||
try {
|
||||
const v = localStorage.getItem("showBaseMapPanel");
|
||||
return v === null ? false : v === "true";
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// Base-Map Panel wurde entfernt
|
||||
// Sichtbarkeit der Koordinaten-Suche (Lupe)
|
||||
const [showCoordinateInput, setShowCoordinateInput] = useState(() => {
|
||||
try {
|
||||
@@ -191,6 +208,29 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Zentrale Steuerung: Nur ein Overlay gleichzeitig
|
||||
// Mögliche Werte: null | 'area' | 'layers' | 'coord' | 'info'
|
||||
const [overlay, setOverlay] = useState(null);
|
||||
|
||||
// Initiale Bestimmung des aktiven Overlays basierend auf bestehenden Flags
|
||||
useEffect(() => {
|
||||
if (showAreaDropdown) setOverlay("area");
|
||||
else if (showLayersPanel) setOverlay("layers");
|
||||
else if (showCoordinateInput) setOverlay("coord");
|
||||
else if (showAppInfoCard) setOverlay("info");
|
||||
else setOverlay(null);
|
||||
// nur beim Mount ausführen
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Flags mit Overlay-State synchronisieren (persistiert weiterhin in bestehenden Effects)
|
||||
useEffect(() => {
|
||||
setShowAreaDropdown(overlay === "area");
|
||||
setShowLayersPanel(overlay === "layers");
|
||||
setShowCoordinateInput(overlay === "coord");
|
||||
setShowAppInfoCard(overlay === "info");
|
||||
}, [overlay]);
|
||||
|
||||
// Flag, ob Nutzer die Polyline-Checkbox manuell betätigt hat
|
||||
// Nutzer-Flag global auf window, damit auch Redux darauf zugreifen kann
|
||||
if (typeof window !== "undefined" && window.userToggledPolyline === undefined) {
|
||||
@@ -262,18 +302,19 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
localStorage.setItem("showAppInfoCard", String(showAppInfoCard));
|
||||
} catch (_) {}
|
||||
}, [showAppInfoCard]);
|
||||
// Persistiere Sichtbarkeit des Area-Dropdowns (Marker-Overlay)
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem("showAreaDropdown", String(showAreaDropdown));
|
||||
} catch (_) {}
|
||||
}, [showAreaDropdown]);
|
||||
// Persistiere Sichtbarkeit des Layer-Panels
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem("showLayersPanel", String(showLayersPanel));
|
||||
} catch (_) {}
|
||||
}, [showLayersPanel]);
|
||||
// Persistiere Sichtbarkeit des Base-Map Panels
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem("showBaseMapPanel", String(showBaseMapPanel));
|
||||
} catch (_) {}
|
||||
}, [showBaseMapPanel]);
|
||||
// Persist-Logik für Base-Map Panel entfernt
|
||||
// Persistiere Sichtbarkeit der Koordinaten-Suche
|
||||
useEffect(() => {
|
||||
try {
|
||||
@@ -1132,7 +1173,8 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
|
||||
{GisStationsStaticDistrict &&
|
||||
GisStationsStaticDistrict.Points?.length > 0 &&
|
||||
showLayersPanel && (
|
||||
showLayersPanel &&
|
||||
!showAreaDropdown && (
|
||||
<MapLayersControlPanel
|
||||
className="z-50"
|
||||
handlePolylineCheckboxChange={handlePolylineCheckboxChange}
|
||||
@@ -1143,6 +1185,39 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
<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 */}
|
||||
<div className="absolute top-3 right-3 z-50 pointer-events-auto flex items-center gap-2">
|
||||
{/* Alarm-Icon - nur anzeigen wenn Alarm aktiv */}
|
||||
{hasActiveAlarm && (
|
||||
<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) */}
|
||||
<button
|
||||
onClick={() => setOverlay(prev => (prev === "area" ? null : "area"))}
|
||||
aria-label="Marker"
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title="Marker"
|
||||
>
|
||||
<MapMarkerIcon className="h-8 w-8" />
|
||||
</button>
|
||||
{/*Lupe: Koordinatensuche ein-/ausblenden */}
|
||||
<button
|
||||
onClick={() => setOverlay(prev => (prev === "coord" ? null : "coord"))}
|
||||
aria-label={
|
||||
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
|
||||
}
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title={
|
||||
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
|
||||
}
|
||||
>
|
||||
<SearchIcon className="h-8 w-8" />
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleEditMode}
|
||||
aria-label={editMode ? "Bearbeitungsmodus deaktivieren" : "Bearbeitungsmodus aktivieren"}
|
||||
@@ -1160,88 +1235,67 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
}
|
||||
disabled={!hasEditRight}
|
||||
>
|
||||
<Icon
|
||||
icon={editMode ? "material-symbols:edit-off-rounded" : "material-symbols:edit-rounded"}
|
||||
className="h-8 w-8 text-blue-900"
|
||||
/>
|
||||
{editMode ? <EditOffIcon className="h-8 w-8" /> : <EditIcon className="h-8 w-8" />}
|
||||
</button>
|
||||
{/* Expand: Karte auf Standardansicht */}
|
||||
<button
|
||||
onClick={handleExpandClick}
|
||||
aria-label="Karte auf Standardansicht"
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1 "
|
||||
title="Karte auf Standardansicht"
|
||||
>
|
||||
<img src="/img/expand-icon.svg" alt="Expand" className="h-8 w-8" />
|
||||
<ExpandIcon className="h-8 w-8" />
|
||||
</button>
|
||||
{/* Lupe: Koordinaten-Suche ein-/ausblenden */}
|
||||
<button
|
||||
onClick={() => setShowBaseMapPanel(v => !v)}
|
||||
aria-label={
|
||||
showBaseMapPanel ? "Kartenhintergrund ausblenden" : "Kartenhintergrund wählen"
|
||||
}
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title={showBaseMapPanel ? "Kartenhintergrund ausblenden" : "Kartenhintergrund wählen"}
|
||||
>
|
||||
<Icon icon="material-symbols:layers-rounded" className="h-8 w-8 text-blue-900" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowLayersPanel(v => !v)}
|
||||
onClick={() => setOverlay(prev => (prev === "layers" ? null : "layers"))}
|
||||
aria-label={showLayersPanel ? "Layer-Panel ausblenden" : "Layer-Panel einblenden"}
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title={showLayersPanel ? "Layer-Panel ausblenden" : "Layer-Panel einblenden"}
|
||||
>
|
||||
<Icon icon="material-symbols:menu-rounded" className="h-8 w-8 text-blue-900" />
|
||||
<MenuIcon className="h-8 w-8" />
|
||||
</button>
|
||||
{/* Lupe: Koordinaten-Suche ein-/ausblenden */}
|
||||
|
||||
<button
|
||||
onClick={() => setShowCoordinateInput(v => !v)}
|
||||
aria-label={
|
||||
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
|
||||
}
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title={
|
||||
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
|
||||
}
|
||||
>
|
||||
<Icon icon="material-symbols:search-rounded" className="h-8 w-8 text-blue-900" />
|
||||
</button>
|
||||
{/* Marker-Icon (line-md) */}
|
||||
<button
|
||||
onClick={() => {}}
|
||||
aria-label="Marker"
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title="Marker"
|
||||
>
|
||||
<Icon icon="line-md:map-marker-filled" className="h-8 w-8 text-blue-900" />
|
||||
</button>
|
||||
{/* Alarm-Icon (mdi) */}
|
||||
<button
|
||||
onClick={() => {}}
|
||||
aria-label="Alarm"
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title="Alarm"
|
||||
>
|
||||
<Icon icon="mdi:alarm-light-outline" className="h-8 w-8 text-blue-900" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAppInfoCard(v => !v)}
|
||||
onClick={() => setOverlay(prev => (prev === "info" ? null : "info"))}
|
||||
aria-label={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
|
||||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||||
title={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:info-rounded"
|
||||
className="text-blue-900 h-8 w-8 pr-1"
|
||||
<InfoIcon
|
||||
className="h-8 w-8 pr-1"
|
||||
title={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{showBaseMapPanel && map && (
|
||||
<BaseMapPanel map={map} onClose={() => setShowBaseMapPanel(false)} />
|
||||
)}
|
||||
{/* Custom Zoom Controls bottom-right, styled in littwin-blue to match app icons */}
|
||||
<div className="absolute bottom-8 right-3 z-50 flex flex-col gap-1">
|
||||
<button
|
||||
data-testid="zoom-in"
|
||||
onClick={() => map?.zoomIn?.()}
|
||||
aria-label="Zoom in"
|
||||
className="rounded-md bg-white/90 hover:bg-white shadow-sm p-1"
|
||||
title="Zoom in"
|
||||
>
|
||||
<PlusIcon className="h-5 w-5 text-littwin-blue" />
|
||||
</button>
|
||||
<button
|
||||
data-testid="zoom-out"
|
||||
onClick={() => map?.zoomOut?.()}
|
||||
aria-label="Zoom out"
|
||||
className="rounded-md bg-white/90 hover:bg-white shadow-sm p-1"
|
||||
title="Zoom out"
|
||||
>
|
||||
<MinusIcon className="h-5 w-5 text-littwin-blue" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Marker/AreaDropdown Panel außerhalb der Button-Leiste platzieren, damit die Position mit den anderen Panels identisch ist */}
|
||||
{overlay === "area" && <AreaDropdown onClose={() => setOverlay(null)} />}
|
||||
{/* BaseMapPanel entfernt */}
|
||||
<CoordinatePopup isOpen={isPopupOpen} coordinates={currentCoordinates} onClose={closePopup} />
|
||||
|
||||
{showAppInfoCard && (
|
||||
<div className="absolute bottom-3 left-3 w-72 p-4 bg-white rounded-lg shadow-md z-50">
|
||||
<div className="absolute top-16 right-3 w-72 p-4 bg-white rounded-lg shadow-md z-50">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<span className="text-black text-lg font-semibold"> TALAS.Map </span>
|
||||
@@ -1250,11 +1304,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={openVersionInfoModal}>
|
||||
<Icon
|
||||
icon="material-symbols:info-rounded"
|
||||
className="text-blue-900 h-8 w-8 pr-1"
|
||||
title="Weitere Infos"
|
||||
/>
|
||||
<InfoIcon className="h-8 w-8 pr-1" title="Weitere Infos" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
79
components/uiWidgets/AreaDropdown.js
Normal file
@@ -0,0 +1,79 @@
|
||||
// /components/uiWidgets/AreaDropdown.js
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setSelectedArea } from "@/redux/slices/selectedAreaSlice";
|
||||
import { selectGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice";
|
||||
import { selectGisSystemStatic } from "@/redux/slices/webservice/gisSystemStaticSlice";
|
||||
|
||||
/**
|
||||
* Kleines Dropdown zur Auswahl der Station (Area_Name),
|
||||
* nutzt dieselbe Datenquelle wie das MapLayersControlPanel.
|
||||
*/
|
||||
const AreaDropdown = ({ onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict) || {};
|
||||
const GisSystemStatic = useSelector(selectGisSystemStatic) || [];
|
||||
|
||||
// Erlaubte Systeme: Allow === 1 und Map === 1
|
||||
const allowedSystems = useMemo(() => {
|
||||
return new Set(
|
||||
(Array.isArray(GisSystemStatic) ? GisSystemStatic : [])
|
||||
.filter(sys => sys.Allow === 1 && sys.Map === 1)
|
||||
.map(sys => sys.IdSystem)
|
||||
);
|
||||
}, [GisSystemStatic]);
|
||||
|
||||
// Uniqe Areas basierend auf Allowed Systems
|
||||
const areaOptions = useMemo(() => {
|
||||
const points = GisStationsStaticDistrict?.Points || [];
|
||||
const seen = new Set();
|
||||
const filtered = points.filter(p => {
|
||||
if (!p?.Area_Name) return false;
|
||||
if (!allowedSystems.has(p.System)) return false;
|
||||
if (seen.has(p.Area_Name)) return false;
|
||||
seen.add(p.Area_Name);
|
||||
return true;
|
||||
});
|
||||
return filtered.map(p => ({ label: p.Area_Name, value: p.IdLD }));
|
||||
}, [GisStationsStaticDistrict, allowedSystems]);
|
||||
|
||||
const handleChange = e => {
|
||||
const selectedIndex = e.target.options.selectedIndex;
|
||||
const label = e.target.options[selectedIndex].text;
|
||||
dispatch(setSelectedArea(label));
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
// Schließe mit ESC
|
||||
useEffect(() => {
|
||||
const onKey = e => {
|
||||
if (e.key === "Escape") onClose?.();
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div className="absolute top-16 right-3 w-72 z-50 bg-white rounded-lg shadow-md">
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div className="text-sm font-semibold mb-2">Station wählen</div>
|
||||
<select
|
||||
onChange={handleChange}
|
||||
className="border p-2 rounded w-full"
|
||||
defaultValue="__default__"
|
||||
>
|
||||
<option value="__default__" disabled>
|
||||
Bitte wählen…
|
||||
</option>
|
||||
{areaOptions.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AreaDropdown;
|
||||
@@ -4,7 +4,7 @@ import React, { useState } from "react";
|
||||
const CoordinateInput = ({ onCoordinatesSubmit }) => {
|
||||
const [coordinates, setCoordinates] = useState("");
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
if (onCoordinatesSubmit) {
|
||||
onCoordinatesSubmit(coordinates);
|
||||
@@ -12,9 +12,21 @@ const CoordinateInput = ({ onCoordinatesSubmit }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="fixed top-5 left-5 z-50 bg-white shadow-lg rounded-lg p-4 w-72">
|
||||
<input type="text" placeholder="Koordinaten eingeben (lat,lng)" value={coordinates} onChange={(e) => setCoordinates(e.target.value)} className="border p-2 rounded w-full mb-2" />
|
||||
<button type="submit" className="bg-blue-500 text-white p-2 rounded w-full hover:bg-blue-600">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="absolute top-16 right-3 z-50 bg-white rounded-lg shadow-md p-4 w-72"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Koordinaten eingeben (lat,lng)"
|
||||
value={coordinates}
|
||||
onChange={e => setCoordinates(e.target.value)}
|
||||
className="border p-2 rounded w-full mb-2"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-littwin-blue text-white p-2 rounded w-full hover:bg-blue-600"
|
||||
>
|
||||
Zu Marker zoomen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -31,7 +31,7 @@ const VersionInfoModal = ({ showVersionInfoModal, closeVersionInfoModal, APP_VER
|
||||
</p>
|
||||
<button
|
||||
onClick={closeVersionInfoModal}
|
||||
className="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700 mx-auto block"
|
||||
className="mt-4 bg-littwin-blue text-white px-4 py-2 rounded mx-auto block"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// components/uiWidgets/baseMapPanel/BaseMapPanel.js
|
||||
// components/uiWidgets/baseMapPanel/BaseMapPanel.js , aus rechliche Grunde nur OSM, dieses Feature ist optional, aktuell nicht genutzt
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import L from "leaflet";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
@@ -241,53 +241,42 @@ function MapLayersControlPanel({ handlePolylineCheckboxChange }) {
|
||||
}, [GisStationsStaticDistrict]);
|
||||
//---------------------------
|
||||
|
||||
// Polyline (Kabelstrecken) abhängig von TALAS (system-1)
|
||||
const onPolylineToggle = checked => {
|
||||
if (editMode) return;
|
||||
|
||||
// Wenn Nutzer Kabelstrecken einschaltet, aber TALAS aktuell ausgeblendet ist,
|
||||
// dann TALAS automatisch aktivieren (sofern erlaubt)
|
||||
const talasKey = "system-1";
|
||||
const talasVisible = !!mapLayersVisibility[talasKey];
|
||||
if (checked && isTalasAllowed && !talasVisible) {
|
||||
dispatch(setLayerVisibility({ layer: talasKey, visibility: true }));
|
||||
|
||||
// Persistiere Sichtbarkeit map/user-spezifisch
|
||||
const mapId2 = localStorage.getItem("currentMapId");
|
||||
const userId2 = localStorage.getItem("currentUserId");
|
||||
const mapStorageKey =
|
||||
mapId2 && userId2 ? `mapLayersVisibility_m${mapId2}_u${userId2}` : "mapLayersVisibility";
|
||||
localStorage.setItem(
|
||||
mapStorageKey,
|
||||
JSON.stringify({ ...mapLayersVisibility, [talasKey]: true })
|
||||
);
|
||||
|
||||
// Event feuern wie an anderer Stelle
|
||||
setTimeout(() => {
|
||||
const event = new Event("visibilityChanged");
|
||||
window.dispatchEvent(event);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Sichtbarkeit der Kabelstrecken setzen
|
||||
handlePolylineCheckboxChange(checked);
|
||||
};
|
||||
|
||||
//---------------------------
|
||||
return (
|
||||
<div
|
||||
id="mainDataSheet"
|
||||
className="absolute top-3 right-3 w-1/6 min-w-[300px] max-w-[400px] z-10 bg-white p-2 rounded-lg shadow-lg"
|
||||
>
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<select
|
||||
onChange={handleAreaChange}
|
||||
id="stationListing"
|
||||
className="border-solid-1 p-2 rounded ml-1 font-semibold"
|
||||
style={{ minWidth: "150px", maxWidth: "200px" }}
|
||||
>
|
||||
<option value="Station wählen">Station wählen</option>
|
||||
{/*
|
||||
...new Map(
|
||||
(GisStationsStaticDistrict.Points || [])
|
||||
.filter(p => !!p.Area_Name)
|
||||
.map(p => [p.Area_Name, p])
|
||||
).values(),
|
||||
*/}
|
||||
{[
|
||||
...new Map(
|
||||
(GisStationsStaticDistrict.Points || [])
|
||||
.filter(p => !!p.Area_Name)
|
||||
.map(p => [p.Area_Name, p])
|
||||
).values(),
|
||||
].map(item => (
|
||||
<option key={item.Area_Name} value={item.IdLD}>
|
||||
{item.Area_Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/*
|
||||
<div className="flex items-center space-x-2">
|
||||
<EditModeToggle />
|
||||
<img
|
||||
src="/img/expand-icon.svg"
|
||||
alt="Expand"
|
||||
className="h-6 w-6 cursor-pointer"
|
||||
onClick={handleIconClick}
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
|
||||
<div className="absolute top-16 right-3 w-72 z-50 bg-white rounded-lg shadow-md">
|
||||
<div id="mainDataSheet" className="flex flex-col gap-4 p-4">
|
||||
{/* Checkboxen mit Untermenüs */}
|
||||
<div className="flex flex-col gap-2">
|
||||
{systemListing.map(system => (
|
||||
@@ -312,7 +301,7 @@ function MapLayersControlPanel({ handlePolylineCheckboxChange }) {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={kabelstreckenVisible}
|
||||
onChange={e => handlePolylineCheckboxChange(e.target.checked)}
|
||||
onChange={e => onPolylineToggle(e.target.checked)}
|
||||
id="polyline-checkbox"
|
||||
disabled={!isTalasAllowed || editMode}
|
||||
/>
|
||||
|
||||
@@ -22,4 +22,73 @@ Verzeichnisstruktur funktioniert.
|
||||
|
||||
---
|
||||
|
||||
## OSM‑basierte, „open“ Quellen
|
||||
|
||||
Diese sind datenrechtlich offen (ODbL bzw. Community-Lizenzen), aber das „kostenlos“ gilt nicht im
|
||||
Sinne unbegrenzter Tile‑Nutzung. Die Tile‑Server werden als Community‑Ressource bereitgestellt –
|
||||
bitte Policies respektieren.
|
||||
|
||||
- osm-standard (OpenStreetMap)
|
||||
- - Key: Nein
|
||||
- - Nutzung: Fair‑Use; für produktive/hohe Last eigenen Tile‑Server/Provider verwenden.
|
||||
- - Attribution: „© OpenStreetMap contributors“
|
||||
|
||||
- osm-humanitarian (HOT)
|
||||
- - Key: Nein
|
||||
- - Nutzung: Fair‑Use; für größere Last die Betreiber kontaktieren bzw. andere Infrastruktur nutzen.
|
||||
- - Attribution: „© OpenStreetMap contributors <br>
|
||||
-
|
||||
- Humanitarian OpenStreetMap Team“ cyclosm
|
||||
- - Key: Nein
|
||||
- - Nutzung: Fair‑Use (bereitgestellt u. a. über OSM France). Für höhere Last
|
||||
Unterstützung/Hostingoptionen prüfen.
|
||||
- - Attribution: „CyclOSM“ + „OpenStreetMap contributors“
|
||||
- Praxis‑Tipps Kleine bis mittlere Nutzung: Die oben genannten „keyless“ Quellen sind oft
|
||||
ausreichend, solange du Attribution setzt und Limits respektierst. Produktion/hohe Last: Nutze
|
||||
einen Anbieter mit Vertrag/Key (z. B. Thunderforest, Tracestrack, MapTiler, Mapbox) oder hoste
|
||||
Tiles selbst. Schlüssel im Client: Für Thunderforest/Tracestrack stehen die Keys im Frontend. Das
|
||||
ist üblich, aber der Key ist sichtbar. Wenn du ihn verbergen willst, richte einen kleinen
|
||||
Tile‑Proxy auf deinem Server ein, der den Key serverseitig anhängt und optional cached.
|
||||
Attribution: Dein BaseMapPanel setzt bereits Attributionsstrings aus config.json. Achte darauf,
|
||||
dass sie je Quelle korrekt sind.
|
||||
|
||||
---
|
||||
|
||||
Kurzantwort: Für kommerzielle Nutzung sind OSM‑Community‑Tile‑Server nicht geeignet. Nutze einen
|
||||
bezahlten Anbieter (z. B. Thunderforest, Tracestrack) oder hoste selbst. Attribution ist immer
|
||||
Pflicht.
|
||||
|
||||
Links und Hinweise je Layer/Provider:
|
||||
|
||||
OpenStreetMap Standard (osm-standard)
|
||||
|
||||
Lizenz/Daten: ODbL, Attribution Pflicht Tile-Server-Policy (keine Produktion/hohe Last):
|
||||
https://operations.osmfoundation.org/policies/tiles/ Urheberrecht/Attribution:
|
||||
https://www.openstreetmap.org/copyright HOT Humanitarian (osm-humanitarian)
|
||||
|
||||
Community-Server (OSM France); keine Produktion/hohe Last Info/Policy OSM France Tiles:
|
||||
https://tile.openstreetmap.fr/ HOT: https://www.hotosm.org/ CyclOSM (cyclosm)
|
||||
|
||||
Community-Server (OSM France); keine Produktion/hohe Last Projektseite: https://www.cyclosm.org/
|
||||
Hinweise/Policy (OSM France): https://tile.openstreetmap.fr/ Wiki:
|
||||
https://wiki.openstreetmap.org/wiki/CyclOSM Carto Light (carto-light / Positron)
|
||||
|
||||
Keylos nutzbar mit Attribution; Fair‑Use, für hohe Last über CARTO‑Pläne Basemaps:
|
||||
https://carto.com/basemaps/ Attribution: https://carto.com/attributions Pricing (Plattform):
|
||||
https://carto.com/pricing/ (bei großem Volumen Sales kontaktieren) Thunderforest (Cycle/Transport u.
|
||||
a.)
|
||||
|
||||
Kommerziell mit API‑Key; Pläne von Free bis Pro Pricing: https://www.thunderforest.com/pricing/
|
||||
Terms/Attribution: https://www.thunderforest.com/terms/ Tracestrack Topo
|
||||
|
||||
API‑Key erforderlich; kostenlose und bezahlte Pläne Übersicht/Pricing:
|
||||
https://www.tracestrack.com/en/maps/ Nutzungsbedingungen: https://www.tracestrack.com/en/terms/
|
||||
Empfehlung:
|
||||
|
||||
Produktion: Nimm Thunderforest oder Tracestrack (oder MapTiler: https://www.maptiler.com/pricing/)
|
||||
oder hoste Tiles selbst. Attribution in der Karte anzeigen (OSM + jeweiliger Anbieter). Soll ich
|
||||
diese Links samt klarer Hinweise kompakt in eure README.md einpflegen?
|
||||
|
||||
---
|
||||
|
||||
[Zurück zur Übersicht](../README.md)
|
||||
|
||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "nodemap",
|
||||
"version": "1.1.361",
|
||||
"version": "1.1.382",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "nodemap",
|
||||
"version": "1.1.361",
|
||||
"version": "1.1.382",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nodemap",
|
||||
"version": "1.1.361",
|
||||
"version": "1.1.382",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
@@ -45,6 +45,11 @@
|
||||
"start": "cross-env NODE_ENV=production node server.js",
|
||||
"export": "next export",
|
||||
"test": "jest",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:slow": "cross-env PW_HEADED=1 PW_SLOWMO=1000 playwright test",
|
||||
"test:e2e:slow:ui": "cross-env PW_HEADED=1 PW_SLOWMO=1000 playwright test --ui",
|
||||
"test:e2e:report": "playwright show-report ./playwright/reports",
|
||||
"prepare": "husky",
|
||||
"bump-version": "node ./scripts/bumpVersion.js"
|
||||
},
|
||||
|
||||
35
playwright.config.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// Playwright test configuration for the NodeMap project
|
||||
// Starts the local Next.js custom server (server.js) and runs tests against http://localhost:3000
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { defineConfig, devices } = require("@playwright/test");
|
||||
|
||||
module.exports = defineConfig({
|
||||
testDir: "./playwright/tests",
|
||||
timeout: 60_000,
|
||||
expect: { timeout: 10_000 },
|
||||
fullyParallel: true,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
// Reporters: keep console-friendly list and generate an HTML report under playwright/reports
|
||||
reporter: [["list"], ["html", { outputFolder: "playwright/reports", open: "never" }]],
|
||||
// Store any runner outputs (attachments, logs) under playwright/test-results
|
||||
outputDir: "playwright/test-results",
|
||||
use: {
|
||||
baseURL: "http://localhost:3000",
|
||||
// Disable artifact generation locally to avoid creating files
|
||||
trace: "off",
|
||||
video: "off",
|
||||
screenshot: "off",
|
||||
headless: process.env.PW_HEADED ? false : true,
|
||||
// Apply slow motion to all actions when PW_SLOWMO is set
|
||||
launchOptions: {
|
||||
slowMo: process.env.PW_SLOWMO ? Number(process.env.PW_SLOWMO) : 0,
|
||||
},
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
// example.spec.js
|
||||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
test("simple test", async ({ page }) => {
|
||||
await page.goto("https://playwright.dev");
|
||||
await expect(page).toHaveTitle(/Playwright/);
|
||||
});
|
||||
119
playwright/tests/mapcomponent.spec.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
// Helper: robust selection for native <select> or custom ARIA comboboxes
|
||||
async function selectStation(page, value) {
|
||||
// Try to find by accessible name first
|
||||
let combo = page.getByRole("combobox", { name: /Station wählen/i });
|
||||
if (!(await combo.count())) {
|
||||
// Fallback: find a container with the label text and locate a select inside
|
||||
const container = page.locator("div").filter({ hasText: "Station wählen" }).last();
|
||||
const selectInContainer = container.locator("select");
|
||||
if (await selectInContainer.count()) {
|
||||
combo = selectInContainer.first();
|
||||
} else {
|
||||
// Final fallback: first visible native select (overlay has only one)
|
||||
combo = page.locator("select:visible").first();
|
||||
}
|
||||
}
|
||||
await expect(combo).toBeVisible();
|
||||
|
||||
const isNative = await combo.evaluate(el => el.tagName === "SELECT");
|
||||
if (isNative) {
|
||||
await expect(combo).toBeEnabled();
|
||||
await expect(combo.locator(`option[value="${value}"]`)).toBeAttached();
|
||||
await combo.selectOption({ value });
|
||||
} else {
|
||||
await combo.click();
|
||||
await page.getByRole("option", { name: new RegExp(value) }).click();
|
||||
}
|
||||
}
|
||||
|
||||
test("MapComponent", async ({ page }) => {
|
||||
// Set initial localStorage BEFORE navigation so the app reads them on load
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("editMode", "false");
|
||||
localStorage.setItem("polylineVisible_m12_u484", "true");
|
||||
localStorage.setItem("currentMapId", "12");
|
||||
localStorage.setItem("currentUserId", "484");
|
||||
localStorage.setItem("mapZoom", "13");
|
||||
localStorage.setItem("kabelstreckenVisible", "false");
|
||||
localStorage.setItem("showBaseMapPanel", "false");
|
||||
localStorage.setItem(
|
||||
"mapLayersVisibility_m12_u484",
|
||||
JSON.stringify({
|
||||
"system-1": true,
|
||||
"system-2": false,
|
||||
"system-3": false,
|
||||
"system-5": false,
|
||||
"system-6": false,
|
||||
"system-7": false,
|
||||
"system-8": false,
|
||||
"system-9": false,
|
||||
"system-10": false,
|
||||
"system-11": false,
|
||||
"system-13": false,
|
||||
"system-30": false,
|
||||
"system-100": false,
|
||||
"system-110": false,
|
||||
"system-111": false,
|
||||
"system-200": false,
|
||||
})
|
||||
);
|
||||
localStorage.setItem("mapCenter", JSON.stringify([53.23938294961826, 8.21434020996094]));
|
||||
localStorage.setItem("markerLink", "undefined");
|
||||
localStorage.setItem("lastElementType", "marker");
|
||||
localStorage.setItem("polylineVisible", "false");
|
||||
localStorage.setItem("showAppInfoCard", "false");
|
||||
localStorage.setItem("showCoordinateInput", "false");
|
||||
localStorage.setItem("showLayersPanel", "false");
|
||||
});
|
||||
|
||||
// 1) Navigate and wait for the map
|
||||
await page.goto("http://localhost:3000/?m=12&u=484");
|
||||
await page.locator("#map").waitFor({ state: "visible", timeout: 20_000 });
|
||||
|
||||
// 2) Optional: verify a key from localStorage at runtime
|
||||
await expect(page.evaluate(() => localStorage.getItem("showLayersPanel"))).resolves.toBe("false");
|
||||
|
||||
// 3) Layer-Panel toggle: expect "einblenden" first (since showLayersPanel=false), then toggle
|
||||
await expect(page.getByRole("button", { name: "Layer-Panel einblenden" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Layer-Panel einblenden" }).click();
|
||||
await expect(page.getByRole("button", { name: "Layer-Panel ausblenden" })).toBeVisible();
|
||||
// 4) Collapse again to restore state
|
||||
await page.getByRole("button", { name: "Layer-Panel ausblenden" }).click();
|
||||
|
||||
// 5) Info-Card toggle: start hidden -> show -> hide -> show again
|
||||
await expect(page.getByRole("button", { name: "Info einblenden" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Info einblenden" }).click();
|
||||
await expect(page.getByRole("button", { name: "Info ausblenden" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Info ausblenden" }).click();
|
||||
await expect(page.getByRole("button", { name: "Info einblenden" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Info einblenden" }).click();
|
||||
await expect(page.locator("div").filter({ hasText: "TALAS.Map Version" }).nth(3)).toBeVisible();
|
||||
|
||||
// 6) Koordinatensuche toggle
|
||||
await page.getByRole("button", { name: "Koordinatensuche einblenden" }).click();
|
||||
await expect(page.locator("form")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Koordinatensuche ausblenden" }).click();
|
||||
|
||||
// 7) Marker setzen und Stationen wählen
|
||||
await page.getByLabel("Marker").click();
|
||||
await expect(page.getByText("Station wählenBitte wählen…")).toBeVisible();
|
||||
await selectStation(page, "50977");
|
||||
await page.getByLabel("Marker").click();
|
||||
await selectStation(page, "50986");
|
||||
await page.getByLabel("Marker").click();
|
||||
await page.getByLabel("Marker").click();
|
||||
await page.getByLabel("Marker").click();
|
||||
await selectStation(page, "50977");
|
||||
await page.getByRole("button", { name: "Karte auf Standardansicht" }).click();
|
||||
//minusIcon
|
||||
await page.getByTestId("zoom-out").click();
|
||||
//wait 3 seconds
|
||||
// plusIcon
|
||||
await page.getByTestId("zoom-in").click(); //plus
|
||||
});
|
||||
|
||||
/* Powershell Befehl ->das führt langsam aus mit 1 Sekunde Pause zwischen den Aktionen
|
||||
$env:PW_HEADED=1; $env:PW_SLOWMO=1000; npx playwright test
|
||||
*/
|
||||
@@ -5,22 +5,7 @@
|
||||
"zoomOutCenter: Zielkoordinaten für Herauszoomen (lat, lng)",
|
||||
"minZoom/maxZoom: erlaubte Zoomstufen pro Quelle"
|
||||
],
|
||||
"tileSources": {
|
||||
"local": {
|
||||
"url": "http://localhost/talas5/TileMap/mapTiles/{z}/{x}/{y}.png",
|
||||
"_comment": "Offline-Kartenquelle (lokal)",
|
||||
"minZoom": 5,
|
||||
"maxZoom": 15
|
||||
},
|
||||
"osm": {
|
||||
"url": "/tiles/{z}/{x}/{y}.png",
|
||||
"_comment": "OpenStreetMap Online-Kartenquelle über Server-Proxy (relativ)",
|
||||
"minZoom": 0,
|
||||
"maxZoom": 19
|
||||
}
|
||||
},
|
||||
"active": "osm",
|
||||
"_comment_active": "Aktive Kartenquelle: 'local' oder 'osm'",
|
||||
|
||||
"center": [53.111111, 8.4625],
|
||||
"_comment_center": "Startmittelpunkt der Karte (lat, lng)",
|
||||
|
||||
@@ -29,6 +14,9 @@
|
||||
|
||||
"basePath": "/talas5",
|
||||
"_comment_basePath": "Basis-URL für API und Routing",
|
||||
"minZoom": 5,
|
||||
"maxZoom": 20,
|
||||
"_comment_zoom": "Globale Zoom-Grenzen (min/max). Kann durch tileSources überschrieben werden.",
|
||||
"debugLog": false,
|
||||
"_comment_debugLog": "Debug-Logging für Client "
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 349 B |
3
public/img/icons/material-symbols/alarm.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 396 B |
3
public/img/icons/material-symbols/edit-off.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18 21l-3-3m-12.728-.364A9 9 0 015.636 5.636m0 0L3 3l3 3m9.364 9.364L18 21M5.636 5.636L3 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
3
public/img/icons/material-symbols/edit.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 432 B |
3
public/img/icons/material-symbols/info.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
4
public/img/icons/material-symbols/map-marker.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="rgb(0, 174, 239)" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25s-7.5-4.108-7.5-11.25a7.5 7.5 0 1115 0z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
3
public/img/icons/material-symbols/menu.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 231 B |
3
public/img/icons/material-symbols/search.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 261 B |
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="jest tests" tests="0" failures="0" errors="0" time="4.822">
|
||||
</testsuites>
|
||||
@@ -1,6 +1,10 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./hooks/**/*.{js,ts,jsx,tsx}"],
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
"./hooks/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
zIndex: {
|
||||
@@ -10,6 +14,9 @@ module.exports = {
|
||||
90: "90",
|
||||
100: "100",
|
||||
},
|
||||
colors: {
|
||||
"littwin-blue": "#00aeef",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"failedTests": []
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export const initializeMap = (
|
||||
let mapCenter = [53.111111, 8.4625];
|
||||
let mapZoom = 12;
|
||||
let minZoom = 5;
|
||||
let maxZoom = 15;
|
||||
let maxZoom = 20;
|
||||
try {
|
||||
if (window && window.__leafletConfig) {
|
||||
config = window.__leafletConfig;
|
||||
@@ -125,6 +125,7 @@ export const initializeMap = (
|
||||
zoom: mapZoom,
|
||||
minZoom: minZoom,
|
||||
maxZoom: maxZoom,
|
||||
// Disable default position; we'll add our own control at bottom-right
|
||||
zoomControl: false,
|
||||
dragging: true,
|
||||
contextmenu: true,
|
||||
@@ -142,6 +143,8 @@ export const initializeMap = (
|
||||
|
||||
initMap.dragging.enable();
|
||||
|
||||
// Do not add the default Leaflet zoom control; we'll render custom React controls in MapComponent
|
||||
|
||||
L.tileLayer(tileLayerUrl, {
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
tileSize: 256,
|
||||
|
||||
@@ -15,7 +15,7 @@ export const zoomIn = (e, map) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let maxZoom = 19;
|
||||
let maxZoom = 20;
|
||||
try {
|
||||
if (window && window.__tileSourceMaxZoom !== undefined) {
|
||||
maxZoom = window.__tileSourceMaxZoom;
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 01 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -15,7 +16,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 31 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -24,7 +26,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 17 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -33,7 +36,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 05 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50984,
|
||||
@@ -42,7 +46,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 20 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -51,7 +56,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 32 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50984,
|
||||
@@ -60,7 +66,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 01 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50977,
|
||||
@@ -69,7 +76,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Station offline",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50977,
|
||||
@@ -78,7 +86,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 01 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -87,7 +96,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Station offline",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50066,
|
||||
@@ -96,7 +106,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "CPL offline",
|
||||
"Feld": 5,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50011,
|
||||
@@ -105,7 +116,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "CPL offline",
|
||||
"Feld": 16,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50011,
|
||||
@@ -114,7 +126,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Wasserdruck aus",
|
||||
"Feld": 16,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50011,
|
||||
@@ -123,7 +136,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Ein",
|
||||
"Feld": 16,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50011,
|
||||
@@ -132,7 +146,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Digitaleingang 1 ON",
|
||||
"Feld": 16,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50000,
|
||||
@@ -141,7 +156,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Ein",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -150,7 +166,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Station offline",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -159,7 +176,8 @@
|
||||
"Co": "#FF00FF",
|
||||
"Me": "Eingang DE 32 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -168,7 +186,8 @@
|
||||
"Co": "#FFFF00",
|
||||
"Me": "KÜG 07: Überspannung kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -177,7 +196,8 @@
|
||||
"Co": "#FFFF00",
|
||||
"Me": "KÜG 08: Überspannung gehend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50977,
|
||||
@@ -186,7 +206,8 @@
|
||||
"Co": "#FFFF00",
|
||||
"Me": "KÜG 08: Überspannung gehend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50984,
|
||||
@@ -195,7 +216,8 @@
|
||||
"Co": "#FFFF00",
|
||||
"Me": "KÜG 08: Überspannung gehend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -204,7 +226,8 @@
|
||||
"Co": "#FFFF00",
|
||||
"Me": "Eingang DE 02 kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -213,7 +236,8 @@
|
||||
"Co": "#FFFF00",
|
||||
"Me": "KÜG 08: Überspannung gehend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -222,7 +246,8 @@
|
||||
"Co": "#FF9900",
|
||||
"Me": "Eingang DE 03 Test Karte kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -231,7 +256,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 01: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -240,7 +266,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 02: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50922,
|
||||
@@ -249,7 +276,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 03: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50000,
|
||||
@@ -258,7 +286,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "über 8V kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50984,
|
||||
@@ -267,7 +296,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 05: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50984,
|
||||
@@ -276,7 +306,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 02: Isolationsminderung kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50984,
|
||||
@@ -285,7 +316,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 06: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50977,
|
||||
@@ -294,7 +326,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 01: Isolationsminderung kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50977,
|
||||
@@ -303,7 +336,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 06: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50977,
|
||||
@@ -312,7 +346,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 05: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50976,
|
||||
@@ -321,7 +356,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "CPL offline",
|
||||
"Feld": 3,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50976,
|
||||
@@ -330,7 +366,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 03: Isolationsminderung kommend",
|
||||
"Feld": 3,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -339,7 +376,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 04: Isolationsminderung kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -348,7 +386,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 02: Isolationsminderung kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -357,7 +396,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 01: Isolationsminderung kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50001,
|
||||
@@ -366,7 +406,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "Sammelstörung kommend",
|
||||
"Feld": 5,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -375,7 +416,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 06: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50975,
|
||||
@@ -384,7 +426,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "KÜG 05: Aderbruch kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50963,
|
||||
@@ -393,7 +436,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "CPL offline",
|
||||
"Feld": 3,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50063,
|
||||
@@ -402,7 +446,8 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "Digitaleingang 1 EIN",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
},
|
||||
{
|
||||
"IdLD": 50000,
|
||||
@@ -411,6 +456,7 @@
|
||||
"Co": "#FF0000",
|
||||
"Me": "über 10V kommend",
|
||||
"Feld": 4,
|
||||
"Icon": 0
|
||||
"Icon": 0,
|
||||
"Alarm": 0
|
||||
}
|
||||
]
|
||||