Merge branch 'develop'

This commit is contained in:
ISA
2024-11-28 15:07:02 +01:00
243 changed files with 15697 additions and 4208 deletions

View File

@@ -1,11 +1,45 @@
#.env.local
#je nach dem Mysql Server, ob localhost freigegeben ist oder die IP Adresse des Servers, manchmal die beide und manchmal nur eine
DB_HOST=localhost #HTTP error! status: 500 wenn auf dem Server ist
#DB_HOST=192.168.10.58
#DB_HOST=10.10.0.13
#DB_HOST=10.10.0.70 # auf localhost blockiert
#DB_USER=root
#DB_PASSWORD="root#$"
#DB_NAME=talas_v5
#DB_PORT=3306
#########################
#NEXT_PUBLIC_BASE_URL="http://10.10.0.13/talas5/devices/"
#NEXT_PUBLIC_SERVER_URL="http://10.10.0.13"
#NEXT_PUBLIC_PROXY_TARGET="http://10.10.0.13"
#NEXT_PUBLIC_ONLINE_TILE_LAYER="http://10.10.0.13:3000/mapTiles/{z}/{x}/{y}.png"
#########################
DB_HOST=10.10.0.70
DB_USER=root
DB_PASSWORD="root#$"
DB_NAME=talas_v5
DB_PORT=3306
######################### Kontetmenü -> Station in tab öffnen
#BASE_URL=http://10.10.0.13/talas5/devices/
#########################
#device nur Verlinkung wenn die gleiche DB ist
NEXT_PUBLIC_BASE_URL="http://10.10.0.70/talas5/devices/"
NEXT_PUBLIC_SERVER_URL="http://10.10.0.70"
NEXT_PUBLIC_PROXY_TARGET="http://10.10.0.70"
#NEXT_PUBLIC_ONLINE_TILE_LAYER="http://10.10.0.13:3000/mapTiles/{z}/{x}/{y}.png"
#########################
#DB_HOST=192.168.10.168
#DB_USER=root
#DB_PASSWORD="root#$"
#DB_NAME=talas_v5
#DB_PORT=3306
#########################
#URLs für den Client (clientseitig)
#NEXT_PUBLIC_BASE_URL="http://192.168.10.168/talas5/devices/"
#NEXT_PUBLIC_SERVER_URL="http://192.168.10.168"
#NEXT_PUBLIC_PROXY_TARGET="http://192.168.10.168"
#NEXT_PUBLIC_ONLINE_TILE_LAYER="http://192.168.10.14:3000/mapTiles/{z}/{x}/{y}.png"
######################### online
NEXT_PUBLIC_ONLINE_TILE_LAYER="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"printWidth": 250
}

1
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1 @@
{}

43
Jenkinsfile vendored
View File

@@ -2,31 +2,36 @@ pipeline {
agent any
stages {
stage('Checkout SCM') {
steps {
checkout([$class: 'GitSCM', branches: [[name: '**']],
doGenerateSubmoduleConfigurations: false,
extensions: [],
userRemoteConfigs: [[url: 'http://172.20.0.2:3000/Ismail/NodeMap', credentialsId: 'd378f013-2f24-417b-9afd-33df5d410ab8']]])
}
}
stage('Check Node.js Version') {
steps {
script {
def nodeVersion = sh(script: 'node --version', returnStdout: true).trim()
echo "Node.js version: ${nodeVersion}"
}
}
}
stage('Install Dependencies') {
steps {
// Installiere npm Abhängigkeiten
sh 'npm install'
script {
sh 'npm install'
}
}
}
stage('Run Tests') {
stage('Run Unit Tests') {
steps {
// Führt Ihre Tests aus
sh 'npm test'
script {
sh 'npm test'
}
}
}
}
post {
always {
// Archivieren Sie die Testberichte, wenn verfügbar
junit '**/test-results.xml'
}
success {
echo 'Die Tests wurden erfolgreich abgeschlossen!'
}
failure {
echo 'Einige Tests sind fehlgeschlagen.'
}
// Weitere Stages ...
}
}

BIN
NodeMap.pdf Normal file

Binary file not shown.

View File

@@ -146,7 +146,7 @@ Bei Problemen während der Weiterentwicklung könnte es hilfreich sein, die Seit
- **DataSheet.js**: Verantwortlich für die Anzeige und Interaktion mit den Layer- und Stationsauswahlen. Ermöglicht das Aktivieren/Deaktivieren von Layern und zeigt Informationen zu geografischen Punkten.
- **MapComponent.js**: Kernkomponente für die Darstellung der Karte. Beinhaltet Logik für das Hinzufügen von Layern, Zoom-Funktionen und andere interaktive Elemente.
- **PoiUpdateModal.js**:Komponente zum Aktualisieren(update) und löschen von Pois.
- **ShowAddStationPopup.js**:Komponente zum hinzufügen von Pois.
- **AddPoiModalWindow.js**:Komponente zum hinzufügen von Pois.
### /public

View File

@@ -1,12 +1,23 @@
module.exports = {
map: () => ({
setView: jest.fn(),
addLayer: jest.fn(),
}),
tileLayer: () => ({
addTo: jest.fn(),
}),
marker: () => ({
addTo: jest.fn(),
}),
// __mocks__/leaflet.js
const L = {
divIcon: jest.fn(() => new L.DivIcon()),
DivIcon: function () {
this.options = {
className: "custom-start-icon",
html: `
<svg width="18" height="18" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<polygon points="10,2 18,18 2,18" fill="black" />
<polygon points="10,5 16,16 4,16" fill="gray" />
</svg>
`,
iconSize: [18, 18],
iconAnchor: [9, 10],
};
},
};
L.DivIcon.prototype = {
constructor: L.DivIcon,
};
module.exports = L;

View File

@@ -0,0 +1,82 @@
// __tests__/components/DataSheet.test.js
import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";
import DataSheet from "../../components/DataSheet";
import { RecoilRoot } from "recoil";
import * as Recoil from "recoil";
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import { gisStationsStaticDistrictState } from "../../store/atoms/gisStationState";
import { gisSystemStaticState } from "../../store/atoms/gisSystemState";
import { mapLayersState } from "../../store/atoms/mapLayersState";
import { selectedAreaState } from "../../store/atoms/selectedAreaState";
import { zoomTriggerState } from "../../store/atoms/zoomTriggerState";
import { poiLayerVisibleState } from "../../store/atoms/poiLayerVisibleState";
// Stelle sicher, dass alle notwendigen Atome korrekt gemockt sind
jest.mock("../../store/atoms/gisStationState", () => ({
gisStationState: {
subscribe: jest.fn(),
getSnapshot: jest.fn(() => []), // Mocked return value
},
}));
jest.mock("../../store/atoms/gisSystemState", () => ({
gisSystemStaticState: {
subscribe: jest.fn(),
getSnapshot: jest.fn(() => []), // Mocked return value
},
}));
describe("DataSheet Component", () => {
//--------------------------------------------------------------------------------
it("renders the basic fields and initial station/system listings", async () => {
render(
<RecoilRoot>
<DataSheet />
</RecoilRoot>
);
// Waiting for asynchronous tasks to complete
await waitFor(() => {
// Check if the specific elements are present after data processing
expect(screen.getByRole("combobox")).toBeInTheDocument();
expect(screen.getByLabelText("POIs")).toBeInTheDocument();
expect(screen.getByRole("option", { name: "Station wählen" })).toBeInTheDocument();
});
// Optionally print out the full DOM for debugging
screen.debug();
});
//--------------------------------------------------------------------------------
test("should open dropdown on click", async () => {
render(
<RecoilRoot>
<DataSheet />
</RecoilRoot>
);
const combobox = screen.getByRole("combobox");
userEvent.click(combobox);
// Überprüfe, ob die Dropdown-Optionen angezeigt werden
expect(screen.getByRole("option", { name: "Station wählen" })).toBeInTheDocument();
});
//--------------------------------------------------------------------------------
test("should toggle checkbox", async () => {
render(
<RecoilRoot>
<DataSheet />
</RecoilRoot>
);
const checkbox = screen.getByLabelText("POIs");
// Stelle sicher, dass die Checkbox nicht angekreuzt ist
expect(checkbox).not.toBeChecked();
// Klicke auf die Checkbox
userEvent.click(checkbox);
// Jetzt sollte sie angekreuzt sein
expect(checkbox).toBeChecked();
});
//--------------------------------------------------------------------------------
});

View File

@@ -0,0 +1,89 @@
// __tests__/MapComponent.test.js
// Ein einfacher Testfall, der sicherstellt, dass die Addition korrekt ist
test("simple addition", () => {
const a = 1;
const b = 2;
const c = a + b;
expect(c).toBe(3); // Überprüft, ob c gleich 3 ist
});
//import L from "leaflet";
//import { checkOverlappingMarkers } from "../utils/mapUtils"; // Passe den Pfad entsprechend an
/* describe("checkOverlappingMarkers", () => {
let map;
beforeEach(() => {
// Erstelle eine neue Leaflet-Karte für jeden Test
map = L.map(document.createElement("div"));
});
it("should group markers by coordinates and add plus icons for overlapping markers", () => {
// Erstelle einige Beispielmarker
const markers = [
L.marker([51.505, -0.09]),
L.marker([51.505, -0.09]),
L.marker([51.51, -0.1]),
];
const plusIcon = L.divIcon({ className: "plus-icon" });
// Rufe die Funktion auf
checkOverlappingMarkers(map, markers, plusIcon);
// Überprüfe, dass die Marker zu Gruppen hinzugefügt wurden
const overlappingGroups = map._layers;
expect(Object.keys(overlappingGroups).length).toBeGreaterThan(0);
// Überprüfe, dass die Plus-Marker hinzugefügt wurden
const plusMarkers = Object.values(overlappingGroups).filter(
(layer) =>
layer.options.icon &&
layer.options.icon.options.className === "plus-icon"
);
expect(plusMarkers.length).toBeGreaterThan(0);
});
it("should handle non-array markers argument gracefully", () => {
const plusIcon = L.divIcon({ className: "plus-icon" });
// Rufe die Funktion mit einem ungültigen Argument auf
checkOverlappingMarkers(map, null, plusIcon);
// Stelle sicher, dass keine Marker hinzugefügt wurden
const layers = map._layers;
expect(Object.keys(layers).length).toBe(0);
});
it("should not add plus markers if there are no overlaps", () => {
// Erstelle einige Beispielmarker
const markers = [
L.marker([51.505, -0.09]),
L.marker([51.51, -0.1]),
L.marker([51.52, -0.12]),
];
const plusIcon = L.divIcon({ className: "plus-icon" });
// Rufe die Funktion auf
checkOverlappingMarkers(map, markers, plusIcon);
// Überprüfe, dass keine Plus-Marker hinzugefügt wurden
const plusMarkers = Object.values(map._layers).filter(
(layer) =>
layer.options.icon &&
layer.options.icon.options.className === "plus-icon"
);
expect(plusMarkers.length).toBe(0);
});
}); */
/*
In diesem Test:
Wird eine neue Leaflet-Karte vor jedem Test erstellt.
checkOverlappingMarkers wird aufgerufen, um zu überprüfen, ob die Funktion die Marker richtig gruppiert und Plus-Icons für überlappende Marker hinzufügt.
Der Test überprüft auch, ob die Funktion ungültige Argumente (wie null für Marker) korrekt behandelt.
Es wird sichergestellt, dass keine Plus-Marker hinzugefügt werden, wenn keine Überlappungen vorliegen.
Stelle sicher, dass du die Pfade und Importe entsprechend deiner Projektstruktur anpasst.
*/

View File

@@ -0,0 +1,40 @@
// __tests__/components/gisPolylines/icons/CircleIcon.test.js
jest.mock("leaflet", () => {
const actualLeaflet = jest.requireActual("leaflet");
return {
...actualLeaflet,
DivIcon: jest.fn().mockImplementation((options) => ({
...options,
options,
_leaflet_id: Math.random(),
})),
};
});
import L from "leaflet";
import CircleIcon from "../../../components/gisPolylines/icons/CircleIcon";
describe("CircleIcon", () => {
test("should be a Leaflet divIcon with correct properties", () => {
// console.log("CircleIcon options:", CircleIcon.options);
expect(CircleIcon).toEqual(
expect.objectContaining({
options: expect.objectContaining({
className: "custom-circle-icon leaflet-marker-icon",
html: '<div style="background-color:gray; width:10px; height:10px; border-radius:50%; border: solid black 1px;"></div>',
iconSize: [25, 25],
iconAnchor: [5, 5],
}),
})
);
expect(CircleIcon.options.className).toContain("custom-circle-icon");
expect(CircleIcon.options.html).toContain("<div");
expect(CircleIcon.options.html).toContain("background-color:gray");
expect(CircleIcon.options.html).toContain("border-radius:50%");
expect(CircleIcon.options.html).toContain("width:10px");
expect(CircleIcon.options.html).toContain("height:10px");
expect(CircleIcon.options.html).toContain("border: solid black 1px");
expect(CircleIcon.options.iconSize).toEqual([25, 25]);
expect(CircleIcon.options.iconAnchor).toEqual([5, 5]);
});
});

View File

@@ -0,0 +1,24 @@
// __tests__/components/gisPolylines/icons/EndIcon.test.js
jest.mock("leaflet", () => {
const actualLeaflet = jest.requireActual("leaflet");
return {
...actualLeaflet,
DivIcon: jest.fn().mockImplementation((options) => ({
...options,
options,
_leaflet_id: Math.random(),
})),
};
});
import L from "leaflet";
import EndIcon from "../../../../components/gisPolylines/icons/EndIcon";
//console.log("console log EndIcon: ", EndIcon);
describe("endIcon", () => {
test("should be a custom-end-icon with correct HTML and styles", () => {
expect(EndIcon.options.className).toBe("custom-end-icon");
expect(EndIcon.options.html).toBe("<div style='background-color: gray; width: 14px; height: 14px; border: solid black 2px;'></div>");
expect(EndIcon.options.iconSize).toEqual([14, 14]);
expect(EndIcon.options.iconAnchor).toEqual([7, 7]);
});
});

View File

@@ -0,0 +1,41 @@
// __tests__/components/gisPolylines/icons/StartIcon.test.js
jest.mock("leaflet", () => {
const actualLeaflet = jest.requireActual("leaflet");
return {
...actualLeaflet,
DivIcon: jest.fn().mockImplementation((options) => ({
...options,
options,
_leaflet_id: Math.random(),
_createIcon: jest.fn(),
_createImg: jest.fn(),
_getIconUrl: jest.fn(),
_initHooks: [],
_initHooksCalled: true,
_setIconStyles: jest.fn(),
callInitHooks: jest.fn(),
createIcon: jest.fn(),
createShadow: jest.fn(),
initialize: jest.fn(),
})),
};
});
import L from "leaflet";
import StartIcon from "../../../../../components/gisPolylines/icons/StartIcon";
describe("StartIcon", () => {
test("should be a Leaflet divIcon with correct properties", () => {
//console.log("StartIcon options:", StartIcon.options);
expect(StartIcon).toEqual(
expect.objectContaining({
options: expect.objectContaining({
className: "custom-start-icon",
html: expect.stringContaining("<svg"),
iconSize: [18, 18],
iconAnchor: [9, 10],
}),
})
);
});
});

View File

@@ -0,0 +1,42 @@
import { renderHook, act, waitFor } from "@testing-library/react";
import useLineData from "../../../hooks/useLineData";
import React from "react";
const mockUrl = "http://mockurl.com";
const mockData1 = {
Statis: [{ IdLD: 1, Modul: "mod1", PrioColor: "red", ModulName: "Module 1", ModulTyp: "Type 1", Message: "Message 1", PrioName: "High", DpName: "DP 1", Value: "Value 1" }],
};
const mockData2 = [{ idLD: 1, idModul: "mod1" }];
global.fetch = jest.fn((url) => {
if (url === mockUrl) {
return Promise.resolve({
json: () => Promise.resolve(mockData1),
});
} else {
return Promise.resolve({
json: () => Promise.resolve(mockData2),
});
}
});
describe("useLineData", () => {
test("should fetch line data and set line colors and tooltip contents", async () => {
const setLineStatusData = jest.fn();
const { result } = renderHook(() => useLineData(mockUrl, setLineStatusData));
await waitFor(
() => {
expect(setLineStatusData).toHaveBeenCalledWith(expect.any(Array));
expect(result.current).toEqual(
expect.objectContaining({
lineColors: expect.any(Object),
tooltipContents: expect.any(Object),
})
);
},
{ timeout: 5000 } // Increase the timeout to 5000ms
);
});
});

View File

@@ -0,0 +1,86 @@
// __tests__/components/pois/AddPoiModalWindow.test.js
import React from "react";
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react";
import "@testing-library/jest-dom";
import AddPoiModalWindow from "../../../components/pois/AddPoiModalWindow";
import { RecoilRoot } from "recoil";
// Mock für die fetch-Funktion
global.fetch = jest.fn((url) => {
if (url === "/api/talas_v5_DB/poiTyp/readPoiTyp") {
return Promise.resolve({
json: () => Promise.resolve([{ idPoiTyp: "1", name: "Type1" }]),
});
} else if (url === "/api/talas5/location_device") {
return Promise.resolve({
json: () => Promise.resolve([{ idLD: "1", name: "Device1" }]),
});
} else if (url === "/api/talas_v5_DB/pois/addLocation") {
return Promise.resolve({ ok: true });
}
});
describe("AddPoiModalWindow", () => {
const mockOnClose = jest.fn();
const mockMap = {
closePopup: jest.fn(),
};
const mockLatLng = { lat: 52.52, lng: 13.405 };
// Mock window.location.reload
beforeAll(() => {
Object.defineProperty(window, "location", {
value: {
...window.location,
reload: jest.fn(),
},
writable: true,
});
});
beforeEach(() => {
fetch.mockClear();
window.location.reload.mockClear(); // Clear the mock before each test
});
test("renders correctly and allows form submission", async () => {
await act(async () => {
render(
<RecoilRoot>
<AddPoiModalWindow onClose={mockOnClose} map={mockMap} latlng={mockLatLng} />
</RecoilRoot>
);
});
// Überprüfen, ob die Formularelemente gerendert werden
expect(screen.getByLabelText(/Name/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Gerät/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Typ/i)).toBeInTheDocument();
expect(screen.getByText(/Lat/i)).toHaveTextContent(`Lat : ${mockLatLng.lat.toFixed(5)}`);
expect(screen.getByText(/Lng/i)).toHaveTextContent(`Lng : ${mockLatLng.lng.toFixed(5)}`);
// Simulieren der Eingabe in das Namensfeld
fireEvent.change(screen.getByLabelText(/Name/i), { target: { value: "New POI" } });
expect(screen.getByLabelText(/Name/i)).toHaveValue("New POI");
// Simulieren der Auswahl eines Geräts
fireEvent.change(screen.getByLabelText(/Gerät/i), { target: { value: "Device1" } });
expect(screen.getByLabelText(/Gerät/i)).toHaveValue("Device1");
// Simulieren der Auswahl eines Typs
fireEvent.change(screen.getByLabelText(/Typ/i), { target: { value: "1" } });
expect(screen.getByLabelText(/Typ/i)).toHaveValue("1");
// Simulieren des Formulareingabeverfahrens
fireEvent.submit(screen.getByRole("button", { name: /POI hinzufügen/i }));
// Warten auf die Formulareinreichung und Überprüfen der Ergebnisse
await waitFor(() => {
expect(mockOnClose).toHaveBeenCalled();
expect(mockMap.closePopup).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith("/api/talas_v5_DB/pois/addLocation", expect.any(Object));
expect(window.location.reload).toHaveBeenCalled(); // Verify that reload was called
});
});
});

View File

@@ -0,0 +1,77 @@
// __tests__/components/pois/AddPoiModalWindowPopup.test.js
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import AddPoiModalWindowPopup from "../../../components/pois/AddPoiModalWindowPopup";
// Mock the AddPoiModalWindow component
jest.mock("../../../components/pois/AddPoiModalWindow", () => ({ onClose, onSubmit, latlng }) => (
<div data-testid="add-poi-modal-window">
<button onClick={() => console.log("Mocked Close Modal Click")}>Close Modal</button>
<button onClick={onSubmit}>Submit</button>
<div>
Coordinates: {latlng.lat}, {latlng.lng}
</div>
</div>
));
describe("AddPoiModalWindowPopup", () => {
const closePopupMock = jest.fn();
const handleAddStationMock = jest.fn();
const popupCoordinatesMock = { lat: 52.52, lng: 13.405 };
// Suppress console.log for the duration of these tests
beforeAll(() => {
jest.spyOn(console, "log").mockImplementation(() => {});
});
// Restore console.log after all tests
afterAll(() => {
console.log.mockRestore();
});
beforeEach(() => {
closePopupMock.mockClear();
handleAddStationMock.mockClear();
});
test("renders the popup when showPopup is true", () => {
render(<AddPoiModalWindowPopup showPopup={true} closePopup={closePopupMock} handleAddStation={handleAddStationMock} popupCoordinates={popupCoordinatesMock} />);
expect(screen.getByTestId("add-poi-modal-window")).toBeInTheDocument();
expect(screen.getByText("Coordinates: 52.52, 13.405")).toBeInTheDocument();
});
test("does not render the popup when showPopup is false", () => {
render(<AddPoiModalWindowPopup showPopup={false} closePopup={closePopupMock} handleAddStation={handleAddStationMock} popupCoordinates={popupCoordinatesMock} />);
expect(screen.queryByTestId("add-poi-modal-window")).not.toBeInTheDocument();
});
test("closes the popup when the top right close button is clicked", () => {
render(<AddPoiModalWindowPopup showPopup={true} closePopup={closePopupMock} handleAddStation={handleAddStationMock} popupCoordinates={popupCoordinatesMock} />);
// Use the aria-label to uniquely identify the close button in the outer component
const closeButton = screen.getByRole("button", { name: "Close" });
// Click the button with aria-label "Close"
fireEvent.click(closeButton);
expect(closePopupMock).toHaveBeenCalledTimes(1);
});
test("submits the form when the submit button is clicked", () => {
render(<AddPoiModalWindowPopup showPopup={true} closePopup={closePopupMock} handleAddStation={handleAddStationMock} popupCoordinates={popupCoordinatesMock} />);
fireEvent.click(screen.getByText("Submit"));
expect(handleAddStationMock).toHaveBeenCalledTimes(1); // This should now pass
});
test('clicking the "Close Modal" button inside AddPoiModalWindow should not close the popup', () => {
render(<AddPoiModalWindowPopup showPopup={true} closePopup={closePopupMock} handleAddStation={handleAddStationMock} popupCoordinates={popupCoordinatesMock} />);
// Click the "Close Modal" button inside the mock
fireEvent.click(screen.getByText("Close Modal"));
expect(closePopupMock).toHaveBeenCalledTimes(0); // Should not call the popup close
});
});

View File

@@ -0,0 +1,75 @@
// __tests__/components/pois/AddPoiModalWindowWrapper.test.js
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import AddPoiModalWindowWrapper from "../../../components/pois/AddPoiModalWindowWrapper";
// Mock the AddPoiModalWindow component to avoid testing its internal implementation here
jest.mock("../../../components/pois/AddPoiModalWindow", () => ({ onClose, onSubmit, latlng }) => (
<div data-testid="add-poi-modal-window">
<button onClick={onClose}>Mocked Close</button>
<button onClick={onSubmit}>Mocked Submit</button>
<div>
Mocked Coordinates: {latlng.lat}, {latlng.lng}
</div>
</div>
));
describe("AddPoiModalWindowWrapper", () => {
const onCloseMock = jest.fn();
const handleAddStationMock = jest.fn();
const latlngMock = { lat: 52.52, lng: 13.405 };
beforeEach(() => {
onCloseMock.mockClear();
handleAddStationMock.mockClear();
});
test("renders the modal when 'show' is true", () => {
render(<AddPoiModalWindowWrapper show={true} onClose={onCloseMock} handleAddStation={handleAddStationMock} latlng={latlngMock} />);
// Check if the modal and its contents are in the document
expect(screen.getByTestId("add-poi-modal-window")).toBeInTheDocument();
expect(screen.getByText("Mocked Coordinates: 52.52, 13.405")).toBeInTheDocument();
});
test("does not render the modal when 'show' is false", () => {
render(<AddPoiModalWindowWrapper show={false} onClose={onCloseMock} handleAddStation={handleAddStationMock} latlng={latlngMock} />);
// Check if the modal is not in the document
expect(screen.queryByTestId("add-poi-modal-window")).not.toBeInTheDocument();
});
test("calls onClose when the close button is clicked", () => {
render(<AddPoiModalWindowWrapper show={true} onClose={onCloseMock} handleAddStation={handleAddStationMock} latlng={latlngMock} />);
// Click the close button
fireEvent.click(screen.getByRole("button", { name: "Close" }));
expect(onCloseMock).toHaveBeenCalledTimes(1);
});
test("calls onClose when the background is clicked", () => {
render(<AddPoiModalWindowWrapper show={true} onClose={onCloseMock} handleAddStation={handleAddStationMock} latlng={latlngMock} />);
// Click the background overlay
fireEvent.click(screen.getByTestId("modal-overlay"));
expect(onCloseMock).toHaveBeenCalledTimes(1);
});
test("does not call onClose when the modal content is clicked", () => {
render(<AddPoiModalWindowWrapper show={true} onClose={onCloseMock} handleAddStation={handleAddStationMock} latlng={latlngMock} />);
// Click the modal content
fireEvent.click(screen.getByRole("dialog"));
expect(onCloseMock).toHaveBeenCalledTimes(0);
});
test("calls handleAddStation when the submit button is clicked", () => {
render(<AddPoiModalWindowWrapper show={true} onClose={onCloseMock} handleAddStation={handleAddStationMock} latlng={latlngMock} />);
// Click the submit button
fireEvent.click(screen.getByText("Mocked Submit"));
expect(handleAddStationMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,117 @@
// __tests__/components/pois/PoiUpdateModal.test.js
import React from "react";
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react";
import "@testing-library/jest-dom";
import PoiUpdateModal from "../../../components/pois/PoiUpdateModal";
import { useRecoilValue } from "recoil";
import { selectedPoiState, currentPoiState } from "../../../store/atoms/poiState";
// Mock the recoil states
jest.mock("recoil");
describe("PoiUpdateModal", () => {
const mockOnClose = jest.fn();
const mockOnSubmit = jest.fn();
const poiDataMock = {
idPoi: "123",
name: "Test POI",
description: "Test Description",
idPoiTyp: "1",
idLD: "2",
};
const selectedPoiMock = {
idPoi: "123",
typ: "Type1",
};
const currentPoiMock = {
idLD: "2",
};
beforeEach(() => {
useRecoilValue.mockImplementation((state) => {
if (state === selectedPoiState) return selectedPoiMock;
if (state === currentPoiState) return currentPoiMock;
});
mockOnClose.mockClear();
mockOnSubmit.mockClear();
global.fetch = jest.fn((url) => {
if (url === "/api/talas_v5_DB/poiTyp/readPoiTyp") {
return Promise.resolve({
json: () => Promise.resolve([{ idPoiTyp: "1", name: "Type1" }]),
});
} else if (url === "/api/talas_v5_DB/locationDevice/locationDevices") {
return Promise.resolve({
json: () => Promise.resolve([{ id: "2", name: "Device1" }]),
});
} else if (url.startsWith("/api/talas_v5_DB/locationDevice/getDeviceId")) {
return Promise.resolve({
json: () => Promise.resolve({ idLD: "2", name: "Device1" }),
});
} else if (url.startsWith("/api/talas_v5_DB/pois/deletePoi")) {
return Promise.resolve({ ok: true });
} else if (url === "/api/talas_v5_DB/pois/updatePoi") {
return Promise.resolve({ ok: true });
}
});
global.alert = jest.fn(); // Mock alert
delete window.location;
window.location = { reload: jest.fn() }; // Mock location.reload
});
test("renders modal with correct data", async () => {
await act(async () => {
render(<PoiUpdateModal onClose={mockOnClose} poiData={poiDataMock} onSubmit={mockOnSubmit} />);
});
expect(screen.getByDisplayValue("Test Description")).toBeInTheDocument();
expect(screen.getByDisplayValue("Device1")).toBeInTheDocument();
expect(screen.getByDisplayValue("Type1")).toBeInTheDocument();
});
test("handles POI update submission", async () => {
await act(async () => {
render(<PoiUpdateModal onClose={mockOnClose} poiData={poiDataMock} onSubmit={mockOnSubmit} />);
});
fireEvent.change(screen.getByLabelText("Beschreibung:"), { target: { value: "Updated Description" } });
fireEvent.click(screen.getByText("POI aktualisieren"));
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith("/api/talas_v5_DB/pois/updatePoi", expect.any(Object));
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
});
test("handles POI deletion", async () => {
window.confirm = jest.fn().mockImplementation(() => true); // Mock confirm dialog to always accept
await act(async () => {
render(<PoiUpdateModal onClose={mockOnClose} poiData={poiDataMock} onSubmit={mockOnSubmit} />);
});
fireEvent.click(screen.getByText("POI löschen"));
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining("/api/talas_v5_DB/pois/deletePoi"), { method: "DELETE" });
expect(mockOnClose).toHaveBeenCalledTimes(1);
expect(window.location.reload).toHaveBeenCalledTimes(1);
expect(global.alert).toHaveBeenCalledWith("POI wurde erfolgreich gelöscht.");
});
});
test("closes the modal when the close button is clicked", async () => {
await act(async () => {
render(<PoiUpdateModal onClose={mockOnClose} poiData={poiDataMock} onSubmit={mockOnSubmit} />);
});
fireEvent.click(screen.getByLabelText("Close"));
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,57 @@
// __tests__/components/pois/PoiUpdateModalWindow.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import PoiUpdateModalWindow from "../../../components/pois/PoiUpdateModalWindow";
import PoiUpdateModal from "../../../components/pois/PoiUpdateModal";
// Mock the PoiUpdateModal component to avoid testing its internal implementation here
jest.mock("../../../components/pois/PoiUpdateModal", () => ({ onClose, poiData, onSubmit, latlng }) => (
<div data-testid="poi-update-modal">
<button onClick={onClose}>Close</button>
<div>POI Data: {poiData ? poiData.name : "No Data"}</div>
<div>LatLng: {latlng ? `${latlng.lat}, ${latlng.lng}` : "No Coordinates"}</div>
</div>
));
describe("PoiUpdateModalWindow", () => {
const closePoiUpdateModalMock = jest.fn();
const currentPoiDataMock = {
idPoi: "123",
name: "Test POI",
description: "Test Description",
idPoiTyp: "1",
idLD: "2",
};
const popupCoordinatesMock = { lat: 52.52, lng: 13.405 };
beforeEach(() => {
closePoiUpdateModalMock.mockClear();
});
test("renders PoiUpdateModal when showPoiUpdateModal is true", () => {
render(<PoiUpdateModalWindow showPoiUpdateModal={true} closePoiUpdateModal={closePoiUpdateModalMock} currentPoiData={currentPoiDataMock} popupCoordinates={popupCoordinatesMock} />);
// Check if the modal and its contents are in the document
expect(screen.getByTestId("poi-update-modal")).toBeInTheDocument();
expect(screen.getByText("POI Data: Test POI")).toBeInTheDocument();
expect(screen.getByText("LatLng: 52.52, 13.405")).toBeInTheDocument();
});
test("does not render PoiUpdateModal when showPoiUpdateModal is false", () => {
render(<PoiUpdateModalWindow showPoiUpdateModal={false} closePoiUpdateModal={closePoiUpdateModalMock} currentPoiData={currentPoiDataMock} popupCoordinates={popupCoordinatesMock} />);
// Check if the modal is not in the document
expect(screen.queryByTestId("poi-update-modal")).not.toBeInTheDocument();
});
test("calls closePoiUpdateModal when the close button is clicked", () => {
render(<PoiUpdateModalWindow showPoiUpdateModal={true} closePoiUpdateModal={closePoiUpdateModalMock} currentPoiData={currentPoiDataMock} popupCoordinates={popupCoordinatesMock} />);
// Click the close button
screen.getByText("Close").click();
expect(closePoiUpdateModalMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,79 @@
// __tests__/components/pois/PoiUpdateModalWrapper.test.js
import React from "react";
import { render, screen, fireEvent, act } from "@testing-library/react";
import "@testing-library/jest-dom";
import PoiUpdateModalWrapper from "../../../components/pois/PoiUpdateModalWrapper";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { currentPoiState, selectedPoiState } from "../../../store/atoms/poiState";
import { poiReadFromDbTriggerAtom } from "../../../store/atoms/poiReadFromDbTriggerAtom";
// Mock Recoil hooks
jest.mock("recoil");
describe("PoiUpdateModalWrapper", () => {
const mockOnClose = jest.fn();
const currentPoiMock = {
idPoi: "123",
name: "Test POI",
description: "Test Description",
idPoiTyp: "1",
idLD: "2",
};
const latlngMock = { lat: 52.52, lng: 13.405 };
beforeEach(() => {
// Mock the recoil values
useRecoilValue.mockImplementation((atom) => {
if (atom === currentPoiState) return currentPoiMock;
if (atom === poiReadFromDbTriggerAtom) return 0;
});
useSetRecoilState.mockImplementation(() => jest.fn());
mockOnClose.mockClear();
// Mock global fetch
global.fetch = jest.fn((url) =>
Promise.resolve({
json: () => {
if (url.includes("getDeviceIdById")) {
return Promise.resolve({ idLD: "2", name: "Device1" });
}
if (url.includes("readPoiTyp")) {
return Promise.resolve([{ idPoiTyp: "1", name: "Type1" }]);
}
if (url.includes("locationDevices")) {
return Promise.resolve([{ id: "2", name: "Device1" }]);
}
return Promise.resolve({});
},
})
);
});
test("renders PoiUpdateModal when 'show' is true", async () => {
await act(async () => {
render(<PoiUpdateModalWrapper show={true} onClose={mockOnClose} latlng={latlngMock} />);
});
// Check if the modal and its contents are in the document
expect(screen.getByDisplayValue("Test Description")).toBeInTheDocument();
});
test("does not render PoiUpdateModal when 'show' is false", () => {
render(<PoiUpdateModalWrapper show={false} onClose={mockOnClose} latlng={latlngMock} />);
// Check if the modal is not in the document
expect(screen.queryByDisplayValue("Test POI")).not.toBeInTheDocument();
});
test("calls onClose when the modal close button is clicked", async () => {
await act(async () => {
render(<PoiUpdateModalWrapper show={true} onClose={mockOnClose} latlng={latlngMock} />);
});
// Simulate closing the modal
fireEvent.click(screen.getByLabelText("Close"));
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,111 @@
// __tests__/components/pois/PoiUtils.test.js
import { createPoiMarkers, addMarkersToMap, updateLocationInDatabase } from "../../../components/pois/PoiUtils";
// Mock Leaflet
jest.mock("leaflet", () => {
const Leaflet = {
marker: jest.fn((latlng, options) => ({
addTo: jest.fn(),
on: jest.fn(),
options: options,
getLatLng: jest.fn(() => ({
lat: latlng[0],
lng: latlng[1],
})),
})),
icon: jest.fn((options) => options),
};
return Leaflet;
});
describe("PoiUtils", () => {
describe("createPoiMarkers", () => {
it("should create markers with correct coordinates and icon", () => {
const poiData = [
{ idPoi: "1", latitude: 52.52, longitude: 13.405 },
{ idPoi: "2", latitude: 48.8566, longitude: 2.3522 },
];
const iconPath = "path/to/icon.png";
const markers = createPoiMarkers(poiData, iconPath);
expect(markers).toHaveLength(2);
markers.forEach((marker, index) => {
expect(marker.options.icon.iconUrl).toBe(iconPath);
expect(marker.options.icon.iconSize).toEqual([25, 41]);
expect(marker.options.id).toBe(poiData[index].idPoi);
});
});
});
describe("addMarkersToMap", () => {
it("should add markers to map and bind events", () => {
const mockMap = {};
const mockLayerGroup = { addLayer: jest.fn() };
const mockMarker = {
addTo: jest.fn(),
on: jest.fn(),
};
const markers = [mockMarker, mockMarker];
addMarkersToMap(markers, mockMap, mockLayerGroup);
markers.forEach((marker) => {
expect(marker.addTo).toHaveBeenCalledWith(mockLayerGroup);
expect(marker.on).toHaveBeenCalledWith("mouseover", expect.any(Function));
expect(marker.on).toHaveBeenCalledWith("mouseout", expect.any(Function));
expect(marker.on).toHaveBeenCalledWith("dragend", expect.any(Function));
});
});
});
describe("updateLocationInDatabase", () => {
beforeEach(() => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({}),
})
);
});
afterEach(() => {
jest.clearAllMocks();
});
it("should call fetch with correct parameters", async () => {
const id = "1";
const newLatitude = 52.52;
const newLongitude = 13.405;
await updateLocationInDatabase(id, newLatitude, newLongitude);
expect(fetch).toHaveBeenCalledWith("/api/talas_v5_DB/pois/updateLocation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id,
latitude: newLatitude,
longitude: newLongitude,
}),
});
});
it("should log error when fetch response is not ok", async () => {
console.error = jest.fn();
global.fetch = jest.fn(() =>
Promise.resolve({
ok: false,
json: () => Promise.resolve({}),
})
);
await updateLocationInDatabase("1", 52.52, 13.405);
expect(console.error).toHaveBeenCalledWith("Fehler beim Aktualisieren der Position");
});
});
});

View File

@@ -0,0 +1,6 @@
// __tests__/utils/mapUtils.test.js
describe("Dummy test suite", () => {
test("dummy test", () => {
expect(true).toBe(true);
});
});

View File

@@ -5,58 +5,75 @@ import { gisSystemStaticState } from "../store/atoms/gisSystemState";
import { mapLayersState } from "../store/atoms/mapLayersState";
import { selectedAreaState } from "../store/atoms/selectedAreaState";
import { zoomTriggerState } from "../store/atoms/zoomTriggerState";
import { poiLayerVisibleState } from "../store/atoms/poiLayerVisible";
import { poiLayerVisibleState } from "../store/atoms/poiLayerVisibleState";
import EditModeToggle from "./EditModeToggle";
import { polylineLayerVisibleState } from "../store/atoms/polylineLayerVisibleState"; // Import für Polyline-Visibility
function DataSheet() {
const [poiVisible, setPoiVisible] = useRecoilState(poiLayerVisibleState);
const setSelectedArea = useSetRecoilState(selectedAreaState);
const [mapLayersVisibility, setMapLayersVisibility] =
useRecoilState(mapLayersState);
const [mapLayersVisibility, setMapLayersVisibility] = useRecoilState(mapLayersState);
const [stationListing, setStationListing] = useState([]);
const [systemListing, setSystemListing] = useState([]);
const GisStationsStaticDistrict = useRecoilValue(
gisStationsStaticDistrictState
);
const GisStationsStaticDistrict = useRecoilValue(gisStationsStaticDistrictState);
const GisSystemStatic = useRecoilValue(gisSystemStaticState);
const setZoomTrigger = useSetRecoilState(zoomTriggerState);
const [polylineVisible, setPolylineVisible] = useRecoilState(polylineLayerVisibleState); // Zustand für Polylines
useEffect(() => {
const storedPoiVisible = localStorage.getItem("poiVisible");
if (storedPoiVisible !== null) {
setPoiVisible(storedPoiVisible === "true");
}
const storedPolylineVisible = localStorage.getItem("polylineVisible");
if (storedPolylineVisible !== null) {
setPolylineVisible(storedPolylineVisible === "true");
}
const storedMapLayersVisibility = localStorage.getItem("mapLayersVisibility");
if (storedMapLayersVisibility) {
setMapLayersVisibility(JSON.parse(storedMapLayersVisibility));
}
}, [setPoiVisible, setMapLayersVisibility]);
const handleAreaChange = (event) => {
const selectedIndex = event.target.options.selectedIndex;
const areaName = event.target.options[selectedIndex].text;
setSelectedArea(areaName);
console.log("Area selected oder areaName in DataSheet.js:", areaName);
};
useEffect(() => {
const allowedSystems = new Set(
GisSystemStatic.filter((system) => system.Allow === 1).map(
(system) => system.IdSystem
)
);
const allowedSystems = new Set(GisSystemStatic.filter((system) => system.Allow === 1).map((system) => system.IdSystem));
const seenNames = new Set();
const filteredAreas = GisStationsStaticDistrict.filter((item) => {
const isUnique =
!seenNames.has(item.Area_Name) && allowedSystems.has(item.System);
const isUnique = !seenNames.has(item.Area_Name) && allowedSystems.has(item.System);
if (isUnique) {
seenNames.add(item.Area_Name);
}
return isUnique;
});
console.log("filterdArea GisStationsStaticDistrict:", filteredAreas);
console.log("GisSystemStatic:", GisSystemStatic);
console.log("allowedSystems:", allowedSystems);
setStationListing(
filteredAreas.map((area, index) => ({
id: index + 1,
name: area.Area_Name,
}))
);
const seenSystemNames = new Set();
const filteredSystems = GisSystemStatic.filter((item) => {
const formattedName = item.Name.replace(/[\s\-]+/g, "");
console.log(formattedName);
const isUnique = !seenSystemNames.has(formattedName) && item.Allow === 1;
if (isUnique) {
seenSystemNames.add(formattedName);
}
return isUnique;
});
setSystemListing(
filteredSystems.map((system, index) => ({
id: index + 1,
@@ -67,35 +84,38 @@ function DataSheet() {
const handleCheckboxChange = (name, event) => {
const { checked } = event.target;
console.log(`Checkbox ${name} checked state:`, checked);
setMapLayersVisibility((prev) => {
const newState = {
...prev,
[name]: checked,
};
console.log(`New mapLayersVisibility state:`, newState);
localStorage.setItem("mapLayersVisibility", JSON.stringify(newState)); // Store in localStorage
return newState;
});
};
const handlePoiCheckboxChange = (event) => {
const { checked } = event.target;
setPoiVisible(checked);
localStorage.setItem("poiVisible", checked); // Store POI visibility in localStorage
};
const handleIconClick = () => {
setSelectedArea("Station wählen");
setZoomTrigger((current) => current + 1);
};
const handlePolylineCheckboxChange = (event) => {
const { checked } = event.target;
setPolylineVisible(checked);
localStorage.setItem("polylineVisible", checked); // Store Polyline visibility in localStorage
};
return (
<div
id="mainDataSheet"
className="absolute top-3 right-3 w-1/6 min-w-[300px] z-10 bg-white p-2 rounded-lg shadow-lg"
>
<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">
<select
onChange={handleAreaChange}
id="stationListing"
className="border-solid-1 p-2 rounded ml-1 font-semibold"
>
<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>
{stationListing.map((station) => (
<option key={station.id} value={station.id}>
@@ -103,37 +123,38 @@ function DataSheet() {
</option>
))}
</select>
<img
src="/img/expand-icon.svg"
alt="Expand"
className="h-6 w-6 ml-2 cursor-pointer"
onClick={handleIconClick}
/>
<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>
{/* Checkboxen in einem gemeinsamen Container */}
<div className="flex flex-col gap-2">
{systemListing.map((system) => (
<React.Fragment key={system.id}>
<input
type="checkbox"
checked={mapLayersVisibility[system.name] || false}
onChange={(e) => handleCheckboxChange(system.name, e)}
/>
<label className="text-sm ml-2">{system.name}</label>
<br />
<div className="flex items-center">
<input type="checkbox" checked={mapLayersVisibility[system.name] || false} onChange={(e) => handleCheckboxChange(system.name, e)} id={`system-${system.id}`} />
<label htmlFor={`system-${system.id}`} className="text-sm ml-2">
{system.name}
</label>
</div>
</React.Fragment>
))}
<input
type="checkbox"
checked={poiVisible}
onChange={(e) => {
const checked = e.target.checked;
setPoiVisible(checked);
console.log(
`POIs sind jetzt ${checked ? "sichtbar" : "nicht sichtbar"}.`
);
}}
/>
<label className="text-sm ml-2">POIs</label>
<div className="flex items-center">
<input type="checkbox" checked={poiVisible} onChange={handlePoiCheckboxChange} id="poi-checkbox" />
<label htmlFor="poi-checkbox" className="text-sm ml-2">
POIs
</label>
</div>
<div className="flex items-center">
<input type="checkbox" checked={polylineVisible} onChange={handlePolylineCheckboxChange} id="polyline-checkbox" />
<label htmlFor="polyline-checkbox" className="text-sm ml-2">
Kabelstrecken
</label>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,38 @@
// /components/EditModeToggle.js
import React, { useState, useEffect } from "react";
import EditOffIcon from "@mui/icons-material/EditOff";
import ModeEditIcon from "@mui/icons-material/ModeEdit";
import Tooltip from "@mui/material/Tooltip"; // Importiere Tooltip von Material-UI
function EditModeToggle() {
const [editMode, setEditMode] = useState(() => localStorage.getItem("editMode") === "true");
const toggleEditMode = () => {
const newEditMode = !editMode;
setEditMode(newEditMode);
localStorage.setItem("editMode", newEditMode);
//Browser neu laden, um die Änderungen anzuwenden
window.location.reload();
};
useEffect(() => {
const storedMode = localStorage.getItem("editMode") === "true";
setEditMode(storedMode);
}, []);
return (
<div onClick={toggleEditMode} style={{ cursor: "pointer" }}>
{editMode ? (
<Tooltip title="Bearbeitungsmodus deaktivieren" placement="top">
<EditOffIcon />
</Tooltip>
) : (
<Tooltip title="Bearbeitungsmodus aktivieren" placement="top">
<ModeEditIcon />
</Tooltip>
)}
</div>
);
}
export default EditModeToggle;

File diff suppressed because it is too large Load Diff

View File

@@ -16,9 +16,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
const [deviceName, setDeviceName] = useState("");
const [idLD, setIdLD] = useState(poiData ? poiData.idLD : "");
const [description, setDescription] = useState(
poiData ? poiData.description : ""
);
const [description, setDescription] = useState(poiData ? poiData.description : "");
useEffect(() => {
if (poiData) {
@@ -37,16 +35,11 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
const fetchDeviceId = async () => {
if (poiData && poiData.idLD) {
try {
const response = await fetch(
`/api/talas_v5_DB/locationDevice/getDeviceIdById?idLD=${poiData.idLD}`
);
const response = await fetch(`/api/talas_v5_DB/locationDevice/getDeviceIdById?idLD=${poiData.idLD}`);
const data = await response.json();
if (data) setDeviceName(data.name);
} catch (error) {
console.error(
"Fehler beim Abrufen der Geräteinformation in PoiUpdateModel.js: ",
error
);
console.error("Fehler beim Abrufen der Geräteinformation in PoiUpdateModel.js: ", error);
}
}
};
@@ -57,12 +50,9 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
const handleDeletePoi = async () => {
if (confirm("Sind Sie sicher, dass Sie diesen POI löschen möchten?")) {
try {
const response = await fetch(
`/api/talas_v5_DB/pois/deletePoi?id=${poiId}`,
{
method: "DELETE",
}
);
const response = await fetch(`/api/talas_v5_DB/pois/deletePoi?id=${poiId}`, {
method: "DELETE",
});
if (response.ok) {
alert("POI wurde erfolgreich gelöscht.");
onClose();
@@ -71,7 +61,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
throw new Error("Fehler beim Löschen des POI.");
}
} catch (error) {
console.error("Fehler beim Löschen des POI:", error);
console.error("Fehler beim Löschen des POI 1:", error);
alert("Fehler beim Löschen des POI.");
}
}
@@ -106,16 +96,13 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
const data = await response.json();
setLocationDeviceData(data);
if (poiData && poiData.idLD) {
const selectedDevice = data.find(
(device) => device.id === poiData.idLD
);
setDeviceName(selectedDevice ? selectedDevice.id : data[0].id);
const selectedDevice = data.find((device) => device.id === poiData.idLD);
setDeviceName(selectedDevice ? selectedDevice.id : data[0].id); // Hier wird die ID als initialer Zustand gesetzt
console.log("Selected Device:", selectedDevice);
console.log("Selected devciceName:", deviceName);
}
} catch (error) {
console.error(
"Fehler beim Abrufen der Standort- und Gerätedaten:",
error
);
console.error("Fehler beim Abrufen der Standort- und Gerätedaten:", error);
}
};
fetchData();
@@ -126,9 +113,10 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
.then((response) => response.json())
.then((data) => {
setLocationDeviceData(data);
const currentDevice = data.find(
(device) => device.idLD === currentPoi.idLD
);
console.log("Standort- und Gerätedaten 3:", data);
console.log("Standort- und Gerätedaten 3 poiData:", poiData);
// Findet das Gerät, das der aktuellen IDLD entspricht
const currentDevice = data.find((device) => device.idLD === currentPoi.idLD);
if (currentDevice) {
setDeviceName(currentDevice.name);
}
@@ -141,9 +129,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
const handleSubmit = async (event) => {
event.preventDefault();
const idLDResponse = await fetch(
`/api/talas_v5_DB/locationDevice/getDeviceId?deviceName=${encodeURIComponent(deviceName)}`
);
const idLDResponse = await fetch(`/api/talas_v5_DB/locationDevice/getDeviceId?deviceName=${encodeURIComponent(deviceName)}`);
const idLDData = await idLDResponse.json();
const idLD = idLDData.idLD;
try {
@@ -166,9 +152,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
window.location.reload();
} else {
const errorResponse = await response.json();
throw new Error(
errorResponse.error || "Fehler beim Aktualisieren des POI."
);
throw new Error(errorResponse.error || "Fehler beim Aktualisieren des POI.");
}
} catch (error) {
console.error("Fehler beim Aktualisieren des POI:", error);
@@ -176,39 +160,37 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
}
};
//ausgewählte poi Informationen in Console anzeigen
console.log("Selected POI:", selectedPoi);
console.log("Selected POI Gerät id in poiUpdateModal.js:", selectedPoi.id);
console.log("Selected POI Typ name in poiUpdateModal.js:", selectedPoi.typ); //als Typ in dropdown menu
console.log("Selected POI Beschreibung in poiUpdateModal.js:", selectedPoi.description);
console.log("Selected POI Gerät deviceId in poiUpdateModal.js:", selectedPoi.deviceId);
return (
<form onSubmit={handleSubmit} className="m-0 p-2 w-full">
<div className="flex items-center mb-4">
<label htmlFor="description" className="block mr-2 flex-none">
Beschreibung:
</label>
<input
type="text"
id="description"
name="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Beschreibung der Station"
className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm"
/>
<input type="text" id="description" name="description" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Beschreibung der Station" className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm" />
</div>
<div className="flex items-center mb-4">
<label htmlFor="deviceName" className="block mr-2 flex-none">
Gerät:
</label>
<select
id="deviceName"
name="deviceName"
value={deviceName}
onChange={(e) => setDeviceName(e.target.value)}
className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm"
>
{locationDeviceData.map((device, index) => (
<option key={index} value={device.id}>
{device.name}
</option>
))}
<select id="deviceName" name="deviceName" value={deviceName} onChange={(e) => setDeviceName(e.target.value)} className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm">
{locationDeviceData.map(
(device, index) => (
console.log("device.id und name:", device),
(
<option key={index} value={device.id}>
{device.name}
</option>
)
)
)}
</select>
</div>
@@ -216,13 +198,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
<label htmlFor="idPoiTyp2" className="block mr-2 flex-none">
Typ:
</label>
<select
id="idPoiTyp2"
name="idPoiTyp2"
value={poiTypeId}
onChange={(e) => setPoiTypeId(e.target.value)}
className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm"
>
<select id="idPoiTyp2" name="idPoiTyp2" value={poiTypeId} onChange={(e) => setPoiTypeId(e.target.value)} className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm">
{poiTypData.map((poiTyp, index) => (
<option key={index} value={poiTyp.idPoiTyp}>
{poiTyp.name}
@@ -239,10 +215,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
POI löschen
</button>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full"
>
<button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full">
POI aktualisieren
</button>
</form>

View File

@@ -23,6 +23,7 @@ export const addMarkersToMap = (markers, map, layerGroup) => {
marker.on("mouseover", () => marker.openPopup());
marker.on("mouseout", () => marker.closePopup());
marker.on("dragend", (e) => {
console.log("Marker wurde verschoben in addMarkersToMap");
const newLat = e.target.getLatLng().lat;
const newLng = e.target.getLatLng().lng;
const markerId = e.target.options.id;
@@ -32,11 +33,7 @@ export const addMarkersToMap = (markers, map, layerGroup) => {
};
// Funktion zum Aktualisieren der Standorte in der Datenbank
export const updateLocationInDatabase = async (
id,
newLatitude,
newLongitude
) => {
export const updateLocationInDatabase = async (id, newLatitude, newLongitude) => {
const response = await fetch("/api/talas_v5_DB/pois/updateLocation", {
method: "POST",
headers: { "Content-Type": "application/json" },

37
components/TestScript.js Normal file
View File

@@ -0,0 +1,37 @@
// components/TestScript.js
import { useEffect } from "react";
import setupPolylinesCode from "!!raw-loader!../utils/setupPolylines.js"; // Lädt die gesamte setupPolylines.js als Text
export default function TestScript() {
useEffect(() => {
// Regulärer Ausdruck für "Stützpunkt entfernen" im Kontextmenü
const removeRegex = /marker\.on\("mouseover", function \(\) {\s*this\.bindContextMenu\({\s*contextmenuItems: \[\s*\{\s*text: "Stützpunkt entfernen"/;
// Regulärer Ausdruck für "Stützpunkt hinzufügen" im Kontextmenü
const addRegex = /contextmenuItems: \[\s*\{\s*text: "Stützpunkt hinzufügen"/;
// Stilvorlagen für das Konsolen-Logging
const successStyle = "color: #fff; background-color: #28a745; padding: 4px 8px; font-size: 14px; border-radius: 4px;";
const failStyle = "color: #fff; background-color: #dc3545; padding: 4px 8px; font-size: 14px; border-radius: 4px;";
const neutralStyle = "color: #006400; font-size: 14px; background-color: #f0f0f0; padding: 4px 8px; border-radius: 4px;";
// Überprüfung für "Stützpunkt entfernen"
if (removeRegex.test(setupPolylinesCode)) {
console.log("%c✔ Test bestanden: Der Text für 'Stützpunkt entfernen' wurde gefunden.", successStyle);
} else {
console.log("%c✘ Test fehlgeschlagen: Der Text für 'Stützpunkt entfernen' wurde nicht gefunden.", failStyle);
}
// Überprüfung für "Stützpunkt hinzufügen"
if (addRegex.test(setupPolylinesCode)) {
console.log("%c✔ Test bestanden: Der Text für 'Stützpunkt hinzufügen' wurde gefunden.", successStyle);
} else {
//console.log("%c✘ Test fehlgeschlagen: Der Text für 'Stützpunkt hinzufügen' wurde nicht gefunden.", failStyle);
}
// Beispiel einer neutralen Nachricht (falls benötigt)
console.log("%c Info: Überprüfung abgeschlossen.", neutralStyle);
}, []);
return null; // Keine visuelle Ausgabe erforderlich
}

View File

@@ -0,0 +1,30 @@
// components/VersionInfoModal.js
import React from "react";
const VersionInfoModal = ({ showVersionInfoModal, closeVersionInfoModal, MAP_VERSION }) => {
return (
<>
{showVersionInfoModal && (
<div className="fixed inset-0 flex items-center justify-center z-50">
<div className="fixed inset-0 bg-black bg-opacity-50" onClick={closeVersionInfoModal}></div>
<div className="bg-white p-6 rounded-lg shadow-lg z-60 max-w-lg mx-auto">
<img src="img/Logo_TALAS.png" alt="TALAS V5 Logo" className="w-1/2 mx-auto my-4" />
<div className="bg-white border p-6 rounded-lg shadow-lg z-60 max-w-lg mx-auto">
<h2 className="text-xl font-bold mb-6 text-start leading-tight">Littwin Systemtechnik GmbH & Co. KG</h2>
<h4 className="text-lg font-bold mb-2 text-start leading-tight">Bürgermeister-Brötje Str. 28</h4>
<h4 className="text-lg font-bold mb-2 text-start leading-tight">D-26180 Rastede</h4>
<h5 className="text-md mb-2 text-start leading-snug">T: +49 4402 9725 77-0</h5>
<h5 className="text-md mb-2 text-start leading-snug">E: kontakt@littwin-systemtechnik.de</h5>
</div>
<p className="text-gray-700 text-center font-bold mt-4 leading-relaxed">TALAS.Map Version {MAP_VERSION}</p>
<button onClick={closeVersionInfoModal} className="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700 mx-auto block">
Schließen
</button>
</div>
</div>
)}
</>
);
};
export default VersionInfoModal;

View File

@@ -0,0 +1,26 @@
// /components/gisPolylines/PolylineContextMenu.js
import React from "react";
const PolylineContextMenu = ({ position, onAddPoint, onRemovePoint, onClose }) => {
return (
<div
style={{
position: "absolute",
top: position.y,
left: position.x,
backgroundColor: "white",
border: "1px solid black",
padding: "10px",
zIndex: 1000,
}}
>
<ul>
<li onClick={onAddPoint}>Stützpunkt hinzufügen</li>
<li onClick={onRemovePoint}>Stützpunkt entfernen</li>
<li onClick={onClose}>Schließen</li>
</ul>
</div>
);
};
export default PolylineContextMenu;

View File

@@ -0,0 +1,12 @@
// /components/gisPolylines/icons/CircleIcon.js
import L from "leaflet";
import "leaflet/dist/leaflet.css";
const CircleIcon = new L.DivIcon({
className: "custom-circle-icon leaflet-marker-icon",
html: '<div style="background-color:gray; width:10px; height:10px; border-radius:50%; border: solid black 1px;"></div>',
iconSize: [25, 25],
iconAnchor: [5, 5],
});
export default CircleIcon;

View File

@@ -0,0 +1,11 @@
// /components/gisPolylines/icons/EndIcon.js
// Viereck als End-Icon für die Route
import L from "leaflet";
const EndIcon = L.divIcon({
className: "custom-end-icon",
html: "<div style='background-color: gray; width: 14px; height: 14px; border: solid black 2px;'></div>", // Graues Viereck
iconSize: [14, 14],
iconAnchor: [7, 7], // Mittelpunkt des Vierecks als Anker
});
export default EndIcon;

View File

@@ -0,0 +1,17 @@
// /components/gisPolylines/icons/StartIcon.js
//Custom triangle icon for draggable markers
import L from "leaflet";
const StartIcon = L.divIcon({
className: "custom-start-icon",
html: `
<svg width="18" height="18" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<polygon points="10,2 18,18 2,18" fill="black" />
<polygon points="10,5 16,16 4,16" fill="gray" />
</svg>
`, // Schwarzes Dreieck innerhalb eines grauen Dreiecks
iconSize: [18, 18],
iconAnchor: [9, 10],
});
export default StartIcon;

View File

@@ -0,0 +1,27 @@
// komponents/gisPolylines/icons/SupportPointIcons
import L from "leaflet";
import "leaflet/dist/leaflet.css";
// Icon für Stützpunkt hinzufügen
export const AddSupportPointIcon = L.divIcon({
className: "custom-add-support-point-icon",
html: `
<div style='background-color:green;border-radius:50%;width:12px;height:12px;border: solid white 2px;'>
<div style='color: white; font-size: 10px; text-align: center; line-height: 12px;'>+</div>
</div>
`,
iconSize: [24, 24],
iconAnchor: [12, 12],
});
// Icon für Stützpunkt entfernen
export const RemoveSupportPointIcon = L.divIcon({
className: "custom-remove-support-point-icon",
html: `
<div style='background-color:red;border-radius:50%;width:12px;height:12px;border: solid white 2px;'>
<div style='color: white; font-size: 10px; text-align: center; line-height: 12px;'>-</div>
</div>
`,
iconSize: [24, 24],
iconAnchor: [12, 12],
});

162
components/imports.js Normal file
View File

@@ -0,0 +1,162 @@
// imports.js
import React, { useEffect, useRef, useState, useCallback } from "react";
import L, { marker } from "leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-contextmenu/dist/leaflet.contextmenu.css";
import "leaflet-contextmenu";
import * as config from "../config/config.js";
import * as urls from "../config/urls.js";
import "leaflet.smooth_marker_bouncing";
import OverlappingMarkerSpiderfier from "overlapping-marker-spiderfier-leaflet";
import DataSheet from "./DataSheet.js";
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import { gisStationsStaticDistrictState } from "../store/atoms/gisStationState.js";
import { gisSystemStaticState } from "../store/atoms/gisSystemState.js";
import { mapLayersState } from "../store/atoms/mapLayersState.js";
import { selectedAreaState } from "../store/atoms/selectedAreaState.js";
import { zoomTriggerState } from "../store/atoms/zoomTriggerState.js";
import { poiTypState } from "../store/atoms/poiTypState.js";
import AddPoiModalWindow from "./pois/AddPoiModalWindow.js";
import { poiReadFromDbTriggerAtom } from "../store/atoms/poiReadFromDbTriggerAtom.js";
import { InformationCircleIcon } from "@heroicons/react/20/solid"; // oder 'outline'
import PoiUpdateModal from "./pois/PoiUpdateModal.js";
import { selectedPoiState } from "../store/atoms/poiState.js";
import { currentPoiState } from "../store/atoms/currentPoiState.js";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { mapIdState, userIdState } from "../store/atoms/urlParameterState.js";
import { poiLayerVisibleState } from "../store/atoms/poiLayerVisibleState.js";
import plusRoundIcon from "./PlusRoundIcon.js";
import { parsePoint, findClosestPoints } from "../utils/geometryUtils.js";
import { insertNewPOI, removePOI, handleEditPoi } from "../utils/poiUtils.js";
import { createAndSetDevices } from "../utils/createAndSetDevices.js";
import { redrawPolyline, restoreMapSettings, checkOverlappingMarkers } from "../utils/mapUtils.js";
import circleIcon from "./gisPolylines/icons/CircleIcon.js";
import startIcon from "./gisPolylines/icons/StartIcon.js";
import endIcon from "./gisPolylines/icons/EndIcon.js";
import { fetchGisStatusStations, fetchPriorityConfig, fetchPoiData, updateLocationInDatabase, fetchUserRights, fetchDeviceNameById } from "../services/apiService.js";
import { addContextMenuToMarker } from "../utils/addContextMenuToMarker.js";
import { MAP_VERSION } from "../config/settings.js";
import * as layers from "../config/layers.js";
import { zoomIn, zoomOut, centerHere } from "../utils/zoomAndCenterUtils.js";
import { initializeMap } from "../utils/initializeMap.js";
import { addItemsToMapContextMenu } from "./useMapContextMenu.js";
import useGmaMarkersLayer from "../hooks/layers/useGmaMarkersLayer.js"; // Import the custom hook
import useTalasMarkersLayer from "../hooks/layers/useTalasMarkersLayer.js"; // Import the custom hook
import useEciMarkersLayer from "../hooks/layers/useEciMarkersLayer.js";
import useGsmModemMarkersLayer from "../hooks/layers/useGsmModemMarkersLayer.js";
import useCiscoRouterMarkersLayer from "../hooks/layers/useCiscoRouterMarkersLayer.js";
import useWagoMarkersLayer from "../hooks/layers/useWagoMarkersLayer.js";
import useSiemensMarkersLayer from "../hooks/layers/useSiemensMarkersLayer.js";
import useOtdrMarkersLayer from "../hooks/layers/useOtdrMarkersLayer.js";
import useWdmMarkersLayer from "../hooks/layers/useWdmMarkersLayer.js";
import useMessstellenMarkersLayer from "../hooks/layers/useMessstellenMarkersLayer.js";
import useTalasiclMarkersLayer from "../hooks/layers/useTalasiclMarkersLayer.js";
import useDauzMarkersLayer from "../hooks/layers/useDauzMarkersLayer.js";
import useSmsfunkmodemMarkersLayer from "../hooks/layers/useSmsfunkmodemMarkersLayer.js";
import useUlafMarkersLayer from "../hooks/layers/useUlafMarkersLayer.js";
import useSonstigeMarkersLayer from "../hooks/layers/useSonstigeMarkersLayer.js";
import handlePoiSelect from "../utils/handlePoiSelect.js";
import { fetchGisStationsStaticDistrict, fetchGisStationsStatusDistrict, fetchGisStationsMeasurements, fetchGisSystemStatic } from "../services/fetchData.js";
import { setupPolylines } from "../utils/setupPolylines.js";
import { setupPOIs } from "../utils/setupPOIs.js";
import VersionInfoModal from "./VersionInfoModal.js";
//--------------------------------------------
import PoiUpdateModalWrapper from "./pois/PoiUpdateModalWrapper";
import AddPoiModalWindowWrapper from "./pois/AddPoiModalWindowWrapper";
import useFetchPoiData from "../hooks/useFetchPoiData";
import usePoiTypData from "../hooks/usePoiTypData";
import useMarkerLayers from "../hooks/useMarkerLayers";
import useLayerVisibility from "../hooks/useLayerVisibility";
import useLineData from "../hooks/useLineData.js";
export {
React,
useEffect,
useRef,
useState,
useCallback,
L,
marker,
config,
urls,
OverlappingMarkerSpiderfier,
DataSheet,
useRecoilState,
useRecoilValue,
useSetRecoilState,
gisStationsStaticDistrictState,
gisSystemStaticState,
mapLayersState,
selectedAreaState,
zoomTriggerState,
poiTypState,
AddPoiModalWindow,
poiReadFromDbTriggerAtom,
InformationCircleIcon,
PoiUpdateModal,
selectedPoiState,
currentPoiState,
ToastContainer,
toast,
mapIdState,
userIdState,
poiLayerVisibleState,
plusRoundIcon,
parsePoint,
findClosestPoints,
insertNewPOI,
removePOI,
createAndSetDevices,
handleEditPoi,
redrawPolyline,
restoreMapSettings,
checkOverlappingMarkers,
circleIcon,
startIcon,
endIcon,
fetchGisStatusStations,
fetchPriorityConfig,
fetchPoiData,
updateLocationInDatabase,
fetchUserRights,
fetchDeviceNameById,
addContextMenuToMarker,
MAP_VERSION,
layers,
zoomIn,
zoomOut,
centerHere,
initializeMap,
addItemsToMapContextMenu,
useGmaMarkersLayer,
useTalasMarkersLayer,
useEciMarkersLayer,
useGsmModemMarkersLayer,
useCiscoRouterMarkersLayer,
useWagoMarkersLayer,
useSiemensMarkersLayer,
useOtdrMarkersLayer,
useWdmMarkersLayer,
useMessstellenMarkersLayer,
useTalasiclMarkersLayer,
useDauzMarkersLayer,
useSmsfunkmodemMarkersLayer,
useUlafMarkersLayer,
useSonstigeMarkersLayer,
handlePoiSelect,
fetchGisStationsStaticDistrict,
fetchGisStationsStatusDistrict,
fetchGisStationsMeasurements,
fetchGisSystemStatic,
setupPolylines,
setupPOIs,
VersionInfoModal,
PoiUpdateModalWrapper,
AddPoiModalWindowWrapper,
useFetchPoiData,
usePoiTypData,
useMarkerLayers,
useLayerVisibility,
useLineData,
};

View File

@@ -0,0 +1,217 @@
// components/pois/AddPoiModalWindow.js
import React, { useState, useEffect } from "react";
import Select from "react-select"; // Importiere react-select
import { useSetRecoilState, useRecoilState } from "recoil";
import { mapLayersState } from "../../store/atoms/mapLayersState";
import { poiReadFromDbTriggerAtom } from "../../store/atoms/poiReadFromDbTriggerAtom";
const AddPoiModalWindow = ({ onClose, map, latlng }) => {
const [poiTypData, setpoiTypData] = useState([]);
const [name, setName] = useState("");
const [poiTypeId, setPoiTypeId] = useState(null); // Verwende null für react-select
const [latitude] = useState(latlng.lat.toFixed(5));
const [longitude] = useState(latlng.lng.toFixed(5));
const setTrigger = useSetRecoilState(poiReadFromDbTriggerAtom); // Verwende useSetRecoilState
const [locationDeviceData, setLocationDeviceData] = useState([]);
const [filteredDevices, setFilteredDevices] = useState([]); // Gefilterte Geräte
const [deviceName, setDeviceName] = useState(null); // Verwende null für react-select
const [mapLayersVisibility] = useRecoilState(mapLayersState); // Um die aktiven Layer zu erhalten
// Map von Systemnamen zu ids (wie zuvor)
const systemNameToIdMap = {
TALAS: 1,
ECI: 2,
ULAF: 3,
GSMModem: 5,
CiscoRouter: 6,
WAGO: 7,
Siemens: 8,
OTDR: 9,
WDM: 10,
GMA: 11,
Messdatensammler: 12,
Messstellen: 13,
TALASICL: 100,
DAUZ: 110,
SMSFunkmodem: 111,
Basisgerät: 200,
};
// API-Abfrage, um die Geräte zu laden
useEffect(() => {
const fetchInitialData = async () => {
try {
const [poiTypResponse, locationDeviceResponse] = await Promise.all([fetch("/api/talas_v5_DB/poiTyp/readPoiTyp"), fetch("/api/talas5/location_device")]);
const poiTypData = await poiTypResponse.json();
setpoiTypData(poiTypData);
const locationDeviceData = await locationDeviceResponse.json();
console.log("Geräte von der API:", locationDeviceData); // Geräte-Daten aus der API anzeigen
setLocationDeviceData(locationDeviceData);
// Filtere die Geräte basierend auf den sichtbaren Systemen
filterDevices(locationDeviceData);
} catch (error) {
console.error("Fehler beim Abrufen der Daten:", error);
}
};
fetchInitialData();
}, []);
// Funktion zum Filtern der Geräte basierend auf den aktiven Systemen (Layern)
const filterDevices = (devices) => {
const activeSystems = Object.keys(mapLayersVisibility).filter((system) => mapLayersVisibility[system]);
console.log("Aktive Systeme:", activeSystems); // Anzeigen der aktiven Systeme
// Mappe aktive Systeme auf ihre ids
const activeSystemIds = activeSystems.map((system) => systemNameToIdMap[system]).filter((id) => id !== undefined);
console.log("Aktive System-IDs:", activeSystemIds); // Anzeigen der aktiven System-IDs
// Filtere die Geräte nach aktiven Systemen basierend auf idsystem_typ
const filtered = devices.filter((device) => activeSystemIds.includes(device.idsystem_typ));
console.log("Gefilterte Geräte:", filtered); // Gefilterte Geräte anzeigen
setFilteredDevices(filtered); // Setze die gefilterten Geräte
};
// Wenn mapLayersVisibility sich ändert, filtere die Geräte erneut
useEffect(() => {
if (locationDeviceData.length > 0) {
filterDevices(locationDeviceData);
}
}, [mapLayersVisibility, locationDeviceData]);
const handleSubmit = async (event) => {
event.preventDefault();
if (!poiTypeId) {
alert("Bitte wählen Sie einen Typ aus.");
return;
}
const formData = {
name,
poiTypeId: poiTypeId.value,
latitude,
longitude,
idLD: filteredDevices.find((device) => device.name === deviceName?.value).idLD,
};
const response = await fetch("/api/talas_v5_DB/pois/addLocation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
if (response.ok) {
setTrigger((trigger) => trigger + 1); // Verwenden des Triggers zur Aktualisierung
onClose();
window.location.reload();
} else {
console.error("Fehler beim Hinzufügen des POI");
}
if (map && typeof map.closePopup === "function") {
map.closePopup();
}
};
// Erstelle Optionen für react-select
const poiTypeOptions = poiTypData.map((poiTyp) => ({
value: poiTyp.idPoiTyp,
label: poiTyp.name,
}));
const deviceOptions = filteredDevices.map((device) => ({
value: device.name,
label: device.name,
}));
// Custom styles for react-select
const customStyles = {
control: (provided) => ({
...provided,
width: "100%",
minWidth: "300px", // Minimum width for the dropdown
maxWidth: "100%", // Maximum width (you can adjust this if needed)
}),
menu: (provided) => ({
...provided,
width: "100%",
minWidth: "300px", // Ensure the dropdown menu stays at the minimum width
}),
};
// Style für größere Breite des Modals und für Inputs
const modalStyles = {
// width: "300px", // größere Breite für das Modal
//maxWidth: "100%", // responsive, passt sich an
//padding: "20px", // Polsterung für das Modal
//backgroundColor: "white", // Hintergrundfarbe
//borderRadius: "8px", // Abgerundete Ecken
//boxShadow: "0px 4px 12px rgba(0, 0, 0, 0.1)", // Schatten für das Modal
};
return (
<form onSubmit={handleSubmit} style={modalStyles} className="m-0 p-2 w-full">
<div className="flex flex-col mb-4">
<label htmlFor="name" className="block mb-2 font-bold text-sm text-gray-700">
Beschreibung :
</label>
<input type="text" id="name" value={name} onChange={(e) => setName(e.target.value)} className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm" />
</div>
{/* React Select for Devices */}
<div className="flex flex-col mb-4">
<label htmlFor="deviceName" className="block mb-2 font-bold text-sm text-gray-700">
Gerät :
</label>
<Select
id="deviceName"
value={deviceName}
onChange={setDeviceName}
options={deviceOptions}
placeholder="Gerät auswählen..."
isClearable
styles={customStyles} // Apply custom styles here
/>
</div>
{/* React Select for POI Types */}
<div className="flex flex-col mb-4">
<label htmlFor="idPoiTyp" className="block mb-2 font-bold text-sm text-gray-700">
Typ:
</label>
<Select
id="idPoiTyp"
value={poiTypeId}
onChange={setPoiTypeId}
options={poiTypeOptions}
placeholder="Typ auswählen..."
styles={customStyles} // Apply custom styles here
/>
</div>
<div className="flex flex-row items-center justify-between mb-4">
<div className="flex flex-col items-center">
<label htmlFor="lat" className="block mb-2 text-xs text-gray-700">
Lat : {latitude}
</label>
</div>
<div className="flex flex-col items-center">
<label htmlFor="lng" className="block mb-2 text-xs text-gray-700">
Lng : {longitude}
</label>
</div>
</div>
<button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full">
POI hinzufügen
</button>
</form>
);
};
export default AddPoiModalWindow;

View File

@@ -0,0 +1,24 @@
// components/pois/AddPoiModalWindowPopup.js
import React from "react";
import AddPoiModalWindow from "./AddPoiModalWindow.js";
const AddPoiModalWindowPopup = ({ showPopup, closePopup, handleAddStation, popupCoordinates }) => {
return (
<>
{showPopup && (
<div className="fixed inset-0 bg-black bg-opacity-10 flex justify-center items-center z-[1000]" onClick={closePopup}>
<div className="relative bg-white p-6 rounded-lg shadow-lg" onClick={(e) => e.stopPropagation()}>
<button onClick={closePopup} className="absolute top-0 right-0 mt-2 mr-2 p-1 text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-gray-600" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<AddPoiModalWindow onClose={closePopup} onSubmit={handleAddStation} latlng={popupCoordinates} />
</div>
</div>
)}
</>
);
};
export default AddPoiModalWindowPopup;

View File

@@ -0,0 +1,30 @@
// components/pois/AddPoiModalWindowWrapper.js
import React from "react";
import AddPoiModalWindow from "./AddPoiModalWindow";
const AddPoiModalWindowWrapper = ({ show, onClose, latlng, handleAddStation }) => {
return (
show && (
<div
className="fixed inset-0 bg-black bg-opacity-10 flex justify-center items-center z-[1000]"
onClick={onClose}
data-testid="modal-overlay" // Hinzugefügt, um den Hintergrund zu identifizieren
>
<div
className="relative bg-white p-6 rounded-lg shadow-lg"
onClick={(e) => e.stopPropagation()}
role="dialog" // Hinzugefügt, um das Dialog-Element zu identifizieren
>
<button onClick={onClose} className="absolute top-0 right-0 mt-2 mr-2 p-1 text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-gray-600" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<AddPoiModalWindow onClose={onClose} onSubmit={handleAddStation} latlng={latlng} />
</div>
</div>
)
);
};
export default AddPoiModalWindowWrapper;

View File

@@ -0,0 +1,256 @@
// /components/pois/PoiUpdateModal.js
import React, { useState, useEffect } from "react";
import Select from "react-select"; // Importiere react-select
import { useRecoilState } from "recoil";
import { selectedPoiState } from "../../store/atoms/poiState";
import { currentPoiState } from "../../store/atoms/currentPoiState";
import { mapLayersState } from "../../store/atoms/mapLayersState";
const PoiUpdateModal = ({ onClose, poiData, onSubmit }) => {
const currentPoi = useRecoilState(currentPoiState);
const selectedPoi = useRecoilState(selectedPoiState);
const [mapLayersVisibility] = useRecoilState(mapLayersState);
const [poiId, setPoiId] = useState(poiData ? poiData.idPoi : "");
const [name, setName] = useState(poiData ? poiData.name : "");
const [poiTypData, setPoiTypData] = useState([]);
const [poiTypeId, setPoiTypeId] = useState(null); // Verwende null für react-select
const [locationDeviceData, setLocationDeviceData] = useState([]);
const [filteredDevices, setFilteredDevices] = useState([]);
const [deviceName, setDeviceName] = useState(poiData ? poiData.deviceName : null); // Verwende null für react-select
const [idLD, setIdLD] = useState(poiData ? poiData.idLD : "");
const [description, setDescription] = useState(poiData ? poiData.description : "");
// Map von Systemnamen zu IDs (wie zuvor)
const systemNameToIdMap = {
TALAS: 1,
ECI: 2,
ULAF: 3,
GSMModem: 5,
CiscoRouter: 6,
WAGO: 7,
Siemens: 8,
OTDR: 9,
WDM: 10,
GMA: 11,
Messdatensammler: 12,
Messstellen: 13,
TALASICL: 100,
DAUZ: 110,
SMSFunkmodem: 111,
Basisgerät: 200,
};
useEffect(() => {
if (poiData) {
setPoiId(poiData.idPoi);
setName(poiData.name);
setPoiTypeId(poiData.idPoiTyp); // Setze den Typ-ID
setIdLD(poiData.idLD);
setDescription(poiData.description);
}
}, [poiData]);
// Fetch POI types and set the current POI type in the react-select dropdown
useEffect(() => {
const fetchPoiTypData = async () => {
try {
const response = await fetch("/api/talas_v5_DB/poiTyp/readPoiTyp");
const data = await response.json();
setPoiTypData(data);
// Prüfe den gespeicherten Typ im localStorage
const storedPoiType = localStorage.getItem("selectedPoiType");
// Finde den passenden Typ in den abgerufenen Daten und setze ihn als ausgewählt
if (storedPoiType) {
const matchingType = data.find((type) => type.name === storedPoiType);
if (matchingType) {
setPoiTypeId({ value: matchingType.idPoiTyp, label: matchingType.name });
}
} else if (poiData && poiData.idPoiTyp) {
// Falls kein Typ im localStorage ist, setze den Typ von poiData
const matchingType = data.find((type) => type.idPoiTyp === poiData.idPoiTyp);
if (matchingType) {
setPoiTypeId({ value: matchingType.idPoiTyp, label: matchingType.name });
}
}
} catch (error) {
console.error("Fehler beim Abrufen der poiTyp Daten:", error);
}
};
fetchPoiTypData();
}, [poiData]);
// Fetch location devices and pre-select the current device
useEffect(() => {
const fetchLocationDevices = async () => {
try {
const response = await fetch("/api/talas5/location_device");
const data = await response.json();
setLocationDeviceData(data);
filterDevices(data);
if (poiData && poiData.idLD) {
const selectedDevice = data.find((device) => device.idLD === poiData.idLD);
setDeviceName(selectedDevice ? { value: selectedDevice.name, label: selectedDevice.name } : null);
}
} catch (error) {
console.error("Fehler beim Abrufen der Standort- und Gerätedaten:", error);
}
};
fetchLocationDevices();
}, [poiData]);
// Funktion zum Filtern der Geräte basierend auf den aktiven Systemen (Layern)
const filterDevices = (devices) => {
const activeSystems = Object.keys(mapLayersVisibility).filter((system) => mapLayersVisibility[system]);
// Mappe aktive Systeme auf ihre ids
const activeSystemIds = activeSystems.map((system) => systemNameToIdMap[system]).filter((id) => id !== undefined);
// Filtere die Geräte nach aktiven Systemen basierend auf idsystem_typ
const filtered = devices.filter((device) => activeSystemIds.includes(device.idsystem_typ));
setFilteredDevices(filtered);
};
const handleSubmit = async (event) => {
event.preventDefault();
const idLDResponse = await fetch(`/api/talas_v5_DB/locationDevice/getDeviceId?deviceName=${encodeURIComponent(deviceName?.value)}`);
const idLDData = await idLDResponse.json();
const idLD = idLDData.idLD;
try {
const response = await fetch("/api/talas_v5_DB/pois/updatePoi", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
idPoi: poiId,
name: name,
description: description,
idPoiTyp: poiTypeId?.value, // Den ausgewählten Typ mitsenden
idLD: idLD,
}),
});
if (response.ok) {
onClose();
window.location.reload();
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error || "Fehler beim Aktualisieren des POI.");
}
} catch (error) {
console.error("Fehler beim Aktualisieren des POI:", error);
alert("Fehler beim Aktualisieren des POI.");
}
};
const handleDeletePoi = async () => {
if (confirm("Sind Sie sicher, dass Sie diesen POI löschen möchten?")) {
try {
const response = await fetch(`/api/talas_v5_DB/pois/deletePoi?id=${poiId}`, {
method: "DELETE",
});
if (response.ok) {
onClose();
window.location.reload(); // Aktualisiert die Seite nach dem Löschen
} else {
throw new Error("Fehler beim Löschen des POI.");
}
} catch (error) {
console.error("Fehler beim Löschen des POI:", error);
alert("Fehler beim Löschen des POI.");
}
}
};
// Erstelle Optionen für react-select
const poiTypeOptions = poiTypData.map((poiTyp) => ({
value: poiTyp.idPoiTyp,
label: poiTyp.name,
}));
const deviceOptions = filteredDevices.map((device) => ({
value: device.name,
label: device.name,
}));
// Custom styles for react-select
const customStyles = {
control: (provided) => ({
...provided,
width: "100%",
minWidth: "300px", // Minimum width for the dropdown
maxWidth: "100%", // Maximum width (you can adjust this if needed)
}),
menu: (provided) => ({
...provided,
width: "100%",
minWidth: "300px", // Ensure the dropdown menu stays at the minimum width
}),
};
return (
<div className="fixed inset-0 bg-black bg-opacity-10 flex justify-center items-center z-[1000]" onClick={onClose}>
<div className="relative bg-white p-6 rounded-lg shadow-lg" onClick={(e) => e.stopPropagation()}>
<button onClick={onClose} className="absolute top-0 right-0 mt-2 mr-2 p-1 text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-gray-600" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<form onSubmit={handleSubmit} className="m-0 p-2 w-full">
<div className="flex flex-col mb-4">
<label htmlFor="description" className="block mb-2 font-bold text-sm text-gray-700">
Beschreibung:
</label>
<input type="text" id="description" name="description" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Beschreibung der Station" className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm" />
</div>
{/* React Select for Devices */}
<div className="flex flex-col mb-4">
<label htmlFor="deviceName" className="block mb-2 font-bold text-sm text-gray-700">
Gerät:
</label>
<Select
id="deviceName"
value={deviceName}
onChange={setDeviceName}
options={deviceOptions} // Options for filtering
placeholder="Gerät auswählen..."
isClearable={true} // Allow clearing the selection
styles={customStyles} // Apply custom styles
/>
</div>
{/* React Select for POI Types */}
<div className="flex flex-col mb-4">
<label htmlFor="idPoiTyp" className="block mb-2 font-bold text-sm text-gray-700">
Typ:
</label>
<Select
id="idPoiTyp"
value={poiTypeId}
onChange={setPoiTypeId}
options={poiTypeOptions} // Options for filtering
placeholder="Typ auswählen..."
isClearable={true}
styles={customStyles} // Apply custom styles
/>
</div>
<button type="button" onClick={handleDeletePoi} className="bg-red-400 hover:bg-red-600 text-white font-bold py-2 px-4 rounded w-full mb-4">
POI löschen
</button>
<button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full">
POI aktualisieren
</button>
</form>
</div>
</div>
);
};
export default PoiUpdateModal;

View File

@@ -0,0 +1,9 @@
// components/pois/PoiUpdateModalWindow.js
import React from "react";
import PoiUpdateModal from "./PoiUpdateModal.js";
const PoiUpdateModalWindow = ({ showPoiUpdateModal, closePoiUpdateModal, currentPoiData, popupCoordinates }) => {
return <>{showPoiUpdateModal && <PoiUpdateModal onClose={closePoiUpdateModal} poiData={currentPoiData} onSubmit={() => {}} latlng={popupCoordinates} />}</>;
};
export default PoiUpdateModalWindow;

View File

@@ -0,0 +1,26 @@
// components/pois/PoiUpdateModalWrapper.js
import React, { useState } from "react";
import PoiUpdateModal from "./PoiUpdateModal";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { currentPoiState, selectedPoiState } from "../../store/atoms/poiState";
import { poiReadFromDbTriggerAtom } from "../../store/atoms/poiReadFromDbTriggerAtom";
const PoiUpdateModalWrapper = ({ show, onClose, latlng }) => {
const setSelectedPoi = useSetRecoilState(selectedPoiState);
const setCurrentPoi = useSetRecoilState(currentPoiState);
const currentPoi = useRecoilValue(currentPoiState);
const poiReadTrigger = useRecoilValue(poiReadFromDbTriggerAtom);
return (
show && (
<PoiUpdateModal
onClose={onClose}
poiData={currentPoi}
onSubmit={() => {}} // Add your submit logic here
latlng={latlng}
/>
)
);
};
export default PoiUpdateModalWrapper;

View File

@@ -0,0 +1,53 @@
// components/pois/PoiUtils.js
import L from "leaflet";
// Funktion, um POI Markers zu erstellen
export const createPoiMarkers = (poiData, iconPath) => {
return poiData.map((location) => {
return L.marker([location.latitude, location.longitude], {
icon: L.icon({
iconUrl: iconPath,
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
draggable: true,
}),
id: location.idPoi,
});
});
};
// Funktion zum Hinzufügen von Markern zur Karte und zum Umgang mit Events
export const addMarkersToMap = (markers, map, layerGroup) => {
markers.forEach((marker) => {
marker.addTo(layerGroup);
marker.on("mouseover", () => marker.openPopup());
marker.on("mouseout", () => marker.closePopup());
marker.on("dragend", (e) => {
console.log("Marker wurde verschoben in addMarkersToMap");
const newLat = e.target.getLatLng().lat;
const newLng = e.target.getLatLng().lng;
const markerId = e.target.options.id;
updateLocationInDatabase(markerId, newLat, newLng);
});
});
};
// Funktion zum Aktualisieren der Standorte in der Datenbank
export const updateLocationInDatabase = async (id, newLatitude, newLongitude) => {
const response = await fetch("/api/talas_v5_DB/pois/updateLocation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id,
latitude: newLatitude,
longitude: newLongitude,
}),
});
if (!response.ok) {
console.error("Fehler beim Aktualisieren der Position");
}
};
// Weitere Funktionen können hier hinzugefügt werden

View File

@@ -1,22 +1,85 @@
import { useState, useCallback } from "react";
// /components/useMapContextMenu.js
import { toast } from "react-toastify";
import { zoomIn, zoomOut, centerHere } from "../utils/zoomAndCenterUtils"; // Assuming these are imported correctly
const useMapContextMenu = (map, hasRights, addStationCallback) => {
const [menuItemAdded, setMenuItemAdded] = useState(false);
const zoomInCallback = (e, map) => {
zoomIn(e, map);
};
const addItemsToMapContextMenu = useCallback(() => {
if (map && !menuItemAdded) {
const zoomOutCallback = (map) => {
zoomOut(map);
};
const centerHereCallback = (e, map) => {
centerHere(e, map);
};
// Funktion zum Anzeigen der Koordinaten
const showCoordinates = (e) => {
alert("Breitengrad: " + e.latlng.lat.toFixed(5) + "\nLängengrad: " + e.latlng.lng.toFixed(5));
};
// Kontextmenü Callback für "POI hinzufügen"
const addStationCallback = (event, hasRights, setShowPopup, setPopupCoordinates) => {
const editMode = localStorage.getItem("editMode") === "true";
hasRights = editMode ? hasRights : undefined;
if (hasRights) {
setPopupCoordinates(event.latlng);
setShowPopup(true);
} else {
toast.error("Benutzer hat keine Berechtigung zum Hinzufügen.", {
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
};
export const addItemsToMapContextMenu = (map, menuItemAdded, setMenuItemAdded, hasRights, setShowPopup, setPopupCoordinates) => {
// Überprüfe den Bearbeitungsmodus in localStorage
const editMode = localStorage.getItem("editMode") === "true";
hasRights = editMode ? hasRights : undefined;
if (!menuItemAdded && map && map.contextmenu) {
map.contextmenu.addItem({
text: "Koordinaten anzeigen",
icon: "img/not_listed_location.png",
callback: showCoordinates,
});
map.contextmenu.addItem({ separator: true });
map.contextmenu.addItem({
text: "Reinzoomen",
icon: "img/zoom_in.png",
callback: (e) => zoomInCallback(e, map),
});
map.contextmenu.addItem({
text: "Rauszoomen",
icon: "img/zoom_out.png",
callback: () => zoomOutCallback(map),
});
map.contextmenu.addItem({
text: "Hier zentrieren",
icon: "img/center_focus.png",
callback: (e) => centerHereCallback(e, map),
});
// wenn localStorage Variable editMode true ist, dann wird der Button "POI hinzufügen" angezeigt
if (editMode) {
map.contextmenu.addItem({ separator: true });
map.contextmenu.addItem({
text: "POI hinzufügen",
icon: "img/add_station.png",
className: "background-red",
callback: (event) => addStationCallback(event, hasRights),
callback: (event) => addStationCallback(event, hasRights, setShowPopup, setPopupCoordinates),
});
setMenuItemAdded(true); // Menüpunkt wurde hinzugefült, Zustand aktualisieren
}
}, [map, menuItemAdded, hasRights, addStationCallback]);
return { addItemsToMapContextMenu };
setMenuItemAdded(true);
}
};
export default useMapContextMenu;

View File

@@ -1,21 +1,22 @@
// /config/config.js
import * as urls from "../config/urls.js";
// Definieren der grundlegenden Umgebungseinstellungen und Konfigurationen der Karte
const mapVersion = "0.5.3"; // Die Version der verwendeten Karte
const standardSideMenu = true; // Einstellung, ob ein standardmäßiges Seitenmenü verwendet wird
const fullSideMenu = false; // Einstellung, ob ein vollständiges Seitenmenü verwendet wird
//const serverURL = "/api"; // Die Basis-URL des Servers, von dem Daten bezogen werden
const serverURL = "http://10.10.0.13";
console.log("serverURL in config:", serverURL);
//const serverURL = "http://10.10.0.13";
//const serverURL = "http://10.10.0.70";
const serverURL = process.env.NEXT_PUBLIC_SERVER_URL;
if (!serverURL) {
throw new Error("Die Umgebungsvariable NEXT_PUBLIC_SERVER_URL ist nicht gesetzt!");
}
console.log("%c 1- serverURL in config:", "color: #006400;", serverURL);
// Initialisieren von Variablen, die später im Browserkontext gesetzt werden
let windowHeight, url_string, url, idMap, idUser;
//Online Daten
let mapGisStationsStaticDistrictUrl,
mapGisStationsStatusDistrictUrl,
mapGisStationsMeasurementsUrl,
mapGisSystemStaticUrl,
mapDataIconUrl,
webserviceGisLinesStatusUrl;
let mapGisStationsStaticDistrictUrl, mapGisStationsStatusDistrictUrl, mapGisStationsMeasurementsUrl, mapGisSystemStaticUrl, mapDataIconUrl, webserviceGisLinesStatusUrl;
// Prüfen, ob das Code im Browser ausgeführt wird
if (typeof window !== "undefined") {
@@ -23,13 +24,14 @@ if (typeof window !== "undefined") {
windowHeight = window.innerHeight; // Die Höhe des Browserfensters
url_string = window.location.href; // Die vollständige URL als String
url = new URL(url_string); // Die URL als URL-Objekt, um Teile der URL einfacher zu handhaben
console.log("URL in config:", url);
console.log("URL origin in config:", url.origin); //http://localhost:3000
console.log("%c 2- URL in config:", "color: #006400; font-size: 16px; background-color: #f0f0f0;", url);
console.log("%c 3- URL origin in config:", "color: #006400;", url.origin); //http://localhost:3000
idMap = url.searchParams.get("m"); // Ein Parameter aus der URL, Standardwert ist '10'
idUser = url.searchParams.get("u"); // Ein weiterer Parameter aus der URL, Standardwert ist '484 admin zu testen von Stationen ausblenden und einblenden in der Card'
console.log(`Parameter 'idMap' : ${idMap}`);
console.log(`Parameter 'idUser': ${idUser}`);
console.log(`4- Parameter 'idMap' : ${idMap}`);
console.log(`5- Parameter 'idUser': ${idUser}`);
// Konstruktion von URLs, die auf spezifische Ressourcen auf dem Server zeigen
//http://localhost:3000/?m=10&u=485
@@ -45,8 +47,9 @@ if (typeof window !== "undefined") {
//webserviceGisLinesStatusUrl = `http://localhost:3000/api/linesColorApi`;
//webserviceGisLinesStatusUrl = `http://localhost:3000/api/linesColorApi`;
//webserviceGisLinesStatusUrl = `${"serverURL"}:3000/api/linesColorApi`;
webserviceGisLinesStatusUrl = `http://localhost:3000/api/linesColorApi`;
// webserviceGisLinesStatusUrl = `http://localhost:3000/api/linesColorApi`;
//webserviceGisLinesStatusUrl = `http://192.168.10.14/talas5/ClientData/WebServiceMap.asmx/GisLinesStatus?idMap=${idMap}`;
webserviceGisLinesStatusUrl = `${serverURL}/talas5/ClientData/WebServiceMap.asmx/GisLinesStatus?idMap=${idMap}`;
//http://10.10.0.13/talas5/ClientData/WebserviceMap.asmx/GisSystemStatic?idMap=12&idUser=484

View File

@@ -1,5 +1,5 @@
// /config/settings.js
// Definieren der grundlegenden Umgebungseinstellungen und Konfigurationen der Karte
export const MAP_VERSION = "1.0.0";
export const MAP_VERSION = "1.0.3";
//export const STANDARD_SIDE_MENU = true;
//export const FULL_SIDE_MENU = false;

View File

@@ -1,23 +1,32 @@
// /sonstige/urls.js
// /config/urls.js
// BASE_URL für Station öffnen in neuer tab und gleicher tab, im localhost gab es keine Probleme mit der Frame
//export const BASE_URL = "http://10.10.0.13/talas5/devices/";
//const baseUrl = "http://localhost:3000/talas5/devices/";
//const baseUrl = "http://192.168.10.14/talas5/devices/";
//----
//Talas_v5 Server
//export const OFFLINE_TILE_LAYER = "/mapTiles/{z}/{x}/{y}.png"; // wenn im von localhost also selben Server die Karte angezeigt wird
//export const OFFLINE_TILE_LAYER = "/mapTiles/{z}/{x}/{y}.png";
export const BASE_URL = "http://10.10.0.13/talas5/devices/";
export const OFFLINE_TILE_LAYER = "/mapTiles/{z}/{x}/{y}.png";
export const ONLINE_TILE_LAYER =
"http://10.10.0.13:3000/mapTiles/{z}/{x}/{y}.png"; //Talas_v5 Server */
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; //Station in Tab öffnen
export const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL; //Die Konstante ist in MapComponent.js und useLineData.js verwendet
export const PROXY_TARGET = process.env.NEXT_PUBLIC_PROXY_TARGET; // damit nodejs auf Port 3000 auf dem Server Talas5 auf Port 80 aufgerufen kann, ansonsten gibt CORS Fehler
export const ONLINE_TILE_LAYER = process.env.NEXT_PUBLIC_ONLINE_TILE_LAYER; //Map von Talas_v5 Server
//-----------------------------------
//export const ONLINE_TILE_LAYER = "http://10.10.0.13:3000/mapTiles/{z}/{x}/{y}.png"; //Map von Talas_v5 Server
// export const PROXY_TARGET = "http://10.10.0.70"; // damit nodejs auf Port 3000 auf dem Server Talas5 auf Port 80 aufgerufen kann, ansonsten gibt CORS Fehler
// export const SERVER_URL = "http://10.10.0.70"; //Die Konstante ist in MapComponent.js und useLineData.js verwendet
// export const BASE_URL = "http://10.10.0.70/talas5/devices/"; //Station in Tab öffnen
// //-----------------------------------
/* export const ONLINE_TILE_LAYER = "http://192.168.10.14:3000/mapTiles/{z}/{x}/{y}.png"; //Map von Talas_v5 Server
export const PROXY_TARGET = "http://192.168.10.167"; // damit nodejs auf Port 3000 auf dem Server Talas5 auf Port 80 aufgerufen kann, ansonsten gibt CORS Fehler
export const SERVER_URL = "http://192.168.10.167"; //Die Konstante ist in MapComponent.js und useLineData.js verwendet
export const BASE_URL = "http://192.168.10.167/talas5/devices/"; //Station in Tab öffnen */
//-----------------------------------
// export const ONLINE_TILE_LAYER = "http://192.168.10.14:3000/mapTiles/{z}/{x}/{y}.png"; //Map von Talas_v5 Server */
// export const PROXY_TARGET = "http://localhost"; // damit nodejs auf Port 3000 auf dem Server Talas5 auf Port 80 aufgerufen kann, ansonsten gibt CORS Fehler
// export const SERVER_URL = "http://localhost"; //in MapComponent.js wird es verwendet
// export const BASE_URL = "http://localhost/talas5/devices/"; //Station in Tab öffnen
//-----------------------------------
//-----------------------------------
// weil ich keine API habe, ansonsten serverURL ist localhost(IP-Adresse) für GisSystemStatic für die Benutzerrechte
//const serverURL = `${protocol}//${hostname}`;
//const serverURL = `${protocol}//${hostname}${port ? `:${port}` : ""}`;
//const serverURL = "http://localhost:3000";
export const SERVER_URL = "http://10.10.0.13";
//export const SERVER_URL = "http://10.10.0.13";
//export const SERVER_URL = "http://10.10.0.70";
// Online Daten URLs
/* export const MAP_GIS_STATIONS_STATIC_DISTRICT_URL = `${SERVER_URL}/talas5/ClientData/WebserviceMap.asmx/GisStationsStaticDistrict?idMap=${c}&idUser=${user}`; //idMap: 10, idUser: 484

16
cypress.config.js Normal file
View File

@@ -0,0 +1,16 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
component: {
devServer: {
framework: "next",
bundler: "webpack",
},
},
});

46
cypress/e2e/spec.cy.js Normal file
View File

@@ -0,0 +1,46 @@
describe("Map Initial Load Test", () => {
it("should load the map with the correct center and zoom", () => {
// Besuche die Seite, auf der die Karte angezeigt wird
cy.visit("http://192.168.10.167:3000/?m=12&u=485");
// Überprüfe, ob das Kartenelement existiert
cy.get("#map").should("be.visible");
// Überprüfe, ob die Karte das korrekte Zentrum und den korrekten Zoom hat
cy.window().then((win) => {
const map = win.L.map;
const center = map.getCenter();
const zoom = map.getZoom();
expect(center.lat).to.be.closeTo(53.111111, 0.0001);
expect(center.lng).to.be.closeTo(8.4625, 0.0001);
expect(zoom).to.eq(12);
});
});
});
describe("Map Context Menu Test", () => {
it("should show context menu on right click", () => {
cy.visit("http://192.168.10.167:3000/?m=12&u=485");
// Rechte Maustaste auf eine Koordinate in der Karte klicken
cy.get("#map").rightclick(400, 300); // Rechtsklick auf eine Position in der Karte
// Überprüfen, ob das Kontextmenü angezeigt wird
cy.contains("Station öffnen (Tab)").should("be.visible");
});
it("should open a new tab when context menu option is clicked", () => {
cy.visit("http://192.168.10.167:3000/?m=12&u=485");
cy.get("#map").rightclick(400, 300);
cy.contains("Station öffnen (Tab)").click();
// Testen, ob ein neuer Tab mit dem richtigen Link geöffnet wird
cy.window().then((win) => {
cy.stub(win, "open").as("windowOpen");
});
cy.get("@windowOpen").should("be.calledWith", "https://example.com"); // Ersetze dies durch die tatsächliche URL
});
});

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,20 @@
describe("Map Initial Load Test", () => {
it("should load the map with the correct center and zoom", () => {
// Besuche die Seite, auf der die Karte angezeigt wird
cy.visit("http://192.168.10.167:3000/?m=12&u=485");
// Überprüfe, ob das Kartenelement existiert
cy.get("#map").should("be.visible");
// Überprüfe, ob die Karte das korrekte Zentrum und den korrekten Zoom hat
cy.window().then((win) => {
const map = win.L.map;
const center = map.getCenter();
const zoom = map.getZoom();
expect(center.lat).to.be.closeTo(53.111111, 0.0001);
expect(center.lng).to.be.closeTo(8.4625, 0.0001);
expect(zoom).to.eq(12);
});
});
});

View File

@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
<!-- Used by Next.js to inject CSS. -->
<div id="__next_css__DO_NOT_USE__"></div>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@@ -0,0 +1,27 @@
// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
import { mount } from 'cypress/react18'
Cypress.Commands.add('mount', mount)
// Example use:
// cy.mount(<MyComponent />)

20
cypress/support/e2e.js Normal file
View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -0,0 +1,49 @@
// hooks/useCiscoRouterMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { checkOverlappingMarkers } from "../../utils/mapUtils";
const useCiscoRouterMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [ciscoRouterMarkers, setCiscoRouterMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(6, setCiscoRouterMarkers, GisSystemStatic, priorityConfig); // Cisco Router
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && ciscoRouterMarkers.length) {
ciscoRouterMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
// Call the function to check for overlapping markers
checkOverlappingMarkers(oms, map);
}
}, [map, ciscoRouterMarkers]);
return ciscoRouterMarkers;
};
export default useCiscoRouterMarkersLayer;

View File

@@ -0,0 +1,45 @@
// hooks/useDauzMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
const useDauzMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [dauzMarkers, setDauzMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(110, setDauzMarkers, GisSystemStatic, priorityConfig); // DAUZ
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && dauzMarkers.length) {
dauzMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
}
}, [map, dauzMarkers, oms]);
return dauzMarkers;
};
export default useDauzMarkersLayer;

View File

@@ -0,0 +1,48 @@
// /hooks/layers/useEciMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { checkOverlappingMarkers } from "../../utils/mapUtils";
const useEciMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [eciMarkers, setEciMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(2, setEciMarkers, GisSystemStatic, priorityConfig); // ECI-System
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && eciMarkers.length) {
eciMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup beim Überfahren mit der Maus öffnen und schließen
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
// Call the function to check for overlapping markers
checkOverlappingMarkers(oms, map);
}
}, [map, eciMarkers]);
return eciMarkers;
};
export default useEciMarkersLayer;

View File

@@ -0,0 +1,81 @@
import { useEffect } from "react";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
const useMarkersLayer = (map, markers, GisStationsMeasurements, GMA, oms) => {
useEffect(() => {
if (!map) return;
// Entferne alte Marker
GMA.clearLayers();
// Hinzufügen neuer Marker
markers.forEach((marker) => {
// Finde die Messungen, die zu diesem Marker gehören
const relevantMeasurements = GisStationsMeasurements.filter((m) => m.Area_Name === marker.options.areaName);
let measurements = {};
let area_name = marker.options.areaName;
let idLD = marker.options.idLD;
relevantMeasurements.forEach((m) => {
measurements[m.Na] = m.Val;
});
// Überprüfe, ob die Messwerte vorhanden sind, und setze Standardwerte
const lt = measurements["LT"] || "---";
const fbt = measurements["FBT"] || "---";
const gt = measurements["GT"] || "---";
const rlf = measurements["RLF"] || "---";
console.log(`Station oder Bereich ${area_name} - LT: ${lt}, FBT: ${fbt}, GT: ${gt}, RLF: ${rlf}`);
console.log(`Station idLD: ${idLD} `);
// Tooltip für den Marker binden
marker.bindTooltip(
`
<div class="p-0 rounded-lg bg-white bg-opacity-90">
<div class="font-bold text-sm text-black">
<span>${area_name}</span>
</div>
<div class="font-bold text-xxs text-blue-700">
<span>LT : ${lt} °C</span>
</div>
<div class="font-bold text-xxs text-red-700">
<span>FBT : ${fbt} °C</span>
</div>
<div class="font-bold text-xxs text-yellow-500">
<span>GT : ${gt}</span>
</div>
<div class="font-bold text-xxs text-green-700">
<span>RLF : ${rlf} %</span>
</div>
</div>
`,
{
permanent: true,
direction: "auto",
offset: [60, 0],
}
);
// Ereignisse für das Öffnen und Schließen des Tooltips
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
// Kontextmenü hinzufügen
addContextMenuToMarker(marker);
// Füge den Marker zur Layer-Gruppe hinzu
GMA.addLayer(marker);
oms.addMarker(marker);
});
map.addLayer(GMA);
}, [map, markers, GisStationsMeasurements, GMA, oms]);
};
export default useMarkersLayer;

View File

@@ -0,0 +1,47 @@
// hooks/useGsmModemMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { checkOverlappingMarkers } from "../../utils/mapUtils";
const useGsmModemMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [gsmModemMarkers, setGsmModemMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(5, setGsmModemMarkers, GisSystemStatic, priorityConfig); // GSM-Modem
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && gsmModemMarkers.length) {
gsmModemMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup beim Überfahren mit der Maus öffnen und schließen
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
// Call the function to check for overlapping markers
checkOverlappingMarkers(oms, map);
}
}, [map, gsmModemMarkers]);
return gsmModemMarkers;
};
export default useGsmModemMarkersLayer;

View File

@@ -0,0 +1,49 @@
// hooks/useLteModemMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { checkOverlappingMarkers } from "../../utils/mapUtils";
const useLteModemMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [lteModemMarkers, setLteModemMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(10, setLteModemMarkers, GisSystemStatic, priorityConfig); // LTE Modems
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && lteModemMarkers.length) {
lteModemMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
// Call the function to check for overlapping markers
checkOverlappingMarkers(oms, map);
}
}, [map, lteModemMarkers]);
return lteModemMarkers;
};
export default useLteModemMarkersLayer;

View File

@@ -0,0 +1,38 @@
// /hooks/layers/useMessstellenMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
const useMessstellenMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [messstellenMarkers, setMessstellenMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(13, setMessstellenMarkers, GisSystemStatic, priorityConfig); // Messstellen
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && messstellenMarkers.length) {
messstellenMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
}
}, [map, messstellenMarkers, oms]);
return messstellenMarkers;
};
export default useMessstellenMarkersLayer;

View File

@@ -0,0 +1,45 @@
// hooks/useOtdrMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices"; // Assuming this function is in poiUtils
const useOtdrMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [otdrMarkers, setOtdrMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(9, setOtdrMarkers, GisSystemStatic, priorityConfig); // OTDR
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && otdrMarkers.length) {
otdrMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
}
}, [map, otdrMarkers, oms]);
return otdrMarkers;
};
export default useOtdrMarkersLayer;

View File

@@ -0,0 +1,49 @@
// hooks/useSiemensMarkersLayer.js
import { useState, useEffect } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
import { checkOverlappingMarkers } from "../../utils/mapUtils";
const useSiemensMarkersLayer = (map, oms, gisSystemStatic, priorityConfig) => {
const [siemensMarkers, setSiemensMarkers] = useState([]);
useEffect(() => {
if (gisSystemStatic && gisSystemStatic.length && map) {
createAndSetDevices(8, setSiemensMarkers, gisSystemStatic, priorityConfig); // Siemens-System
}
}, [gisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && siemensMarkers.length) {
siemensMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
// Call the function to check for overlapping markers
checkOverlappingMarkers(oms, map);
}
}, [map, siemensMarkers, oms]);
return siemensMarkers;
};
export default useSiemensMarkersLayer;

View File

@@ -0,0 +1,54 @@
// hooks/useSmsfunkmodemMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import "leaflet-contextmenu";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
const useSmsfunkmodemMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [smsfunkmodemMarkers, setSmsfunkmodemMarkers] = useState([]);
useEffect(() => {
if (map && GisSystemStatic) {
const markers = GisSystemStatic.filter((station) => station.System === 111).map((station) => {
const marker = L.marker([station.Latitude, station.Longitude], {
icon: L.icon({
iconUrl: "/img/icons/pois/sms-funkmodem.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
}),
id: station.id,
areaName: station.Area_Name,
draggable: false,
}).bindPopup(`
<div>
<b class="text-xl text-black-700">${station.Area_Name || "Unbekannt"}</b><br>
${station.Description || "No Description"}<br>
</div>
`);
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
return marker;
});
setSmsfunkmodemMarkers(markers);
markers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
});
}
}, [map, GisSystemStatic, priorityConfig]);
return smsfunkmodemMarkers;
};
export default useSmsfunkmodemMarkersLayer;

View File

@@ -0,0 +1,45 @@
// hooks/useSonstigeMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
const useSonstigeMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [sonstigeMarkers, setSonstigeMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(200, setSonstigeMarkers, GisSystemStatic, priorityConfig); // Sonstige
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && sonstigeMarkers.length) {
sonstigeMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
}
}, [map, sonstigeMarkers, oms]);
return sonstigeMarkers;
};
export default useSonstigeMarkersLayer;

View File

@@ -0,0 +1,102 @@
// /hooks/useTalasMarkers.js
import { useEffect, useState } from "react";
import L from "leaflet";
import "leaflet-contextmenu";
import { useRecoilValue } from "recoil";
import { mapLayersState } from "../../store/atoms/mapLayersState.js";
import { selectedAreaState } from "../../store/atoms/selectedAreaState.js";
import { zoomTriggerState } from "../../store/atoms/zoomTriggerState.js";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker.js";
import { checkOverlappingMarkers } from "../../utils/mapUtils.js";
import plusRoundIcon from "../../components/PlusRoundIcon.js";
import { gisStationsStaticDistrictState } from "../../store/atoms/gisStationState.js";
const useTalasMarkers = (map, oms, layers, priorityConfig) => {
const [talasMarkers, setTalasMarkers] = useState([]);
const mapLayersVisibility = useRecoilValue(mapLayersState);
const selectedArea = useRecoilValue(selectedAreaState);
const zoomTrigger = useRecoilValue(zoomTriggerState);
const GisStationsStaticDistrict = useRecoilValue(gisStationsStaticDistrictState);
// Funktion zum Erstellen und Setzen der Marker
const createAndSetDevices = (systemId, setMarkers, GisSystemStatic, priorityConfig) => {
const markers = GisSystemStatic.filter((station) => station.System === systemId).map((station) => {
const marker = L.marker([station.Latitude, station.Longitude], {
title: station.Name,
contextmenu: true,
contextmenuItems: [],
});
marker.bindPopup(`<b>${station.Name}</b><br>${station.Description}`);
if (priorityConfig.includes(station.Priority)) {
marker.setIcon(
L.icon({
iconUrl: `/icons/priority_${station.Priority}.png`,
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
})
);
}
return marker;
});
setMarkers(markers);
};
useEffect(() => {
if (map && talasMarkers.length) {
talasMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
map.addLayer(layers.TALAS);
checkOverlappingMarkers(oms, map, plusRoundIcon);
}
}, [map, talasMarkers]);
useEffect(() => {
if (!map || !talasMarkers) return;
const toggleLayer = (isVisible) => {
if (isVisible) {
talasMarkers.forEach((marker) => marker.addTo(map));
} else {
talasMarkers.forEach((marker) => map.removeLayer(marker));
}
};
toggleLayer(mapLayersVisibility.TALAS);
}, [map, talasMarkers, mapLayersVisibility.TALAS]);
useEffect(() => {
if (selectedArea && map) {
const station = GisStationsStaticDistrict.find((s) => s.Area_Name === selectedArea);
if (station) {
map.flyTo([station.X, station.Y], 14);
}
}
}, [selectedArea, map, GisStationsStaticDistrict]);
useEffect(() => {
if (zoomTrigger && map) {
map.flyTo([51.41321407879154, 7.739617925303934], 7);
}
}, [zoomTrigger, map]);
return [talasMarkers, setTalasMarkers, createAndSetDevices];
};
export default useTalasMarkers;

View File

@@ -0,0 +1,35 @@
import { useEffect, useState } from "react";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
const useTalasMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [talasMarkers, setTalasMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(1, setTalasMarkers, GisSystemStatic, priorityConfig); // TALAS-System
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && talasMarkers.length && oms) {
talasMarkers.forEach((marker) => {
oms.addMarker(marker); // Erst zu OMS hinzufügen
marker.addTo(map); // Dann zum Map hinzufügen
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker); // Kontextmenü-Event hinzufügen
});
}
}, [map, talasMarkers, oms]);
return talasMarkers;
};
export default useTalasMarkersLayer;

View File

@@ -0,0 +1,45 @@
// hooks/useTalasiclMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
const useTalasiclMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [talasiclMarkers, setTalasiclMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(100, setTalasiclMarkers, GisSystemStatic, priorityConfig); // TALASICL
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && talasiclMarkers.length) {
talasiclMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
}
}, [map, talasiclMarkers, oms]);
return talasiclMarkers;
};
export default useTalasiclMarkersLayer;

View File

@@ -0,0 +1,76 @@
// hooks/useUlafMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
//import { fetchDeviceNameById } from "../services/apiService";
const useUlafMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [ulafMarkers, setUlafMarkers] = useState([]);
useEffect(() => {
if (!map || !GisSystemStatic) return;
const markers = [];
GisSystemStatic.forEach((station) => {
if (station.System === 0) {
// Adjust the condition to match ULAF system identification
const marker = L.marker([station.Lat, station.Lon], {
icon: L.icon({
iconUrl: "/img/icons/ulaf.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
}),
id: station.id,
name: station.name,
description: station.description,
});
marker.bindPopup(`
<div>
<b class="text-xl text-black-700">${station.name || "Unbekannt"}</b><br>
${station.description || "Keine Beschreibung"}
</div>
`);
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
marker.on("click", async () => {
//const deviceName = await fetchDeviceNameById(station.idLD);
marker
.bindPopup(
`
<div>
<b class="text-xl text-black-700">${station.name || "Unbekannt"}</b><br>
${deviceName}<br>
${station.description || "Keine Beschreibung"}
</div>
`
)
.openPopup();
});
markers.push(marker);
if (map) marker.addTo(map);
if (oms) oms.addMarker(marker);
addContextMenuToMarker(marker);
}
});
setUlafMarkers(markers);
return () => {
markers.forEach((marker) => map.removeLayer(marker));
};
}, [map, GisSystemStatic, oms, priorityConfig]);
return ulafMarkers;
};
export default useUlafMarkersLayer;

View File

@@ -0,0 +1,49 @@
// hooks/useWagoMarkersLayer.js
import { useState, useEffect } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
import { checkOverlappingMarkers } from "../../utils/mapUtils";
const useWagoMarkersLayer = (map, oms, gisSystemStatic, priorityConfig) => {
const [wagoMarkers, setWagoMarkers] = useState([]);
useEffect(() => {
if (gisSystemStatic && gisSystemStatic.length && map) {
createAndSetDevices(7, setWagoMarkers, gisSystemStatic, priorityConfig); // WAGO-System
}
}, [gisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && wagoMarkers.length) {
wagoMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
// Call the function to check for overlapping markers
checkOverlappingMarkers(oms, map);
}
}, [map, wagoMarkers, oms]);
return wagoMarkers;
};
export default useWagoMarkersLayer;

View File

@@ -0,0 +1,45 @@
// hooks/useWdmMarkersLayer.js
import { useEffect, useState } from "react";
import L from "leaflet";
import { addContextMenuToMarker } from "../../utils/addContextMenuToMarker";
import { createAndSetDevices } from "../../utils/createAndSetDevices";
const useWdmMarkersLayer = (map, oms, GisSystemStatic, priorityConfig) => {
const [wdmMarkers, setWdmMarkers] = useState([]);
useEffect(() => {
if (GisSystemStatic && GisSystemStatic.length && map) {
createAndSetDevices(10, setWdmMarkers, GisSystemStatic, priorityConfig); // WDM
}
}, [GisSystemStatic, map, priorityConfig]);
useEffect(() => {
if (map && wdmMarkers.length) {
wdmMarkers.forEach((marker) => {
marker.addTo(map);
oms.addMarker(marker);
// Popup on mouseover and mouseout
marker.on("mouseover", function () {
this.openPopup();
});
marker.on("mouseout", function () {
this.closePopup();
});
addContextMenuToMarker(marker);
});
// Disable map context menu
map.options.contextmenu = false;
map.options.contextmenuItems = [];
oms.map.options.contextmenu = false;
oms.map.options.contextmenuItems = [];
}
}, [map, wdmMarkers, oms]);
return wdmMarkers;
};
export default useWdmMarkersLayer;

View File

@@ -0,0 +1,15 @@
// /hooks/useCreateAndSetDevices.js
import { useEffect } from "react";
import { useRecoilState } from "recoil";
import { polylineEventsDisabledState } from "../store/atoms/polylineEventsDisabledState";
import { createAndSetDevices } from "../utils/createAndSetDevices";
const useCreateAndSetDevices = (systemId, setMarkersFunction, GisSystemStatic, priorityConfig) => {
const [polylineEventsDisabled, setPolylineEventsDisabled] = useRecoilState(polylineEventsDisabledState);
useEffect(() => {
createAndSetDevices(systemId, setMarkersFunction, GisSystemStatic, priorityConfig, setPolylineEventsDisabled);
}, [systemId, setMarkersFunction, GisSystemStatic, priorityConfig, setPolylineEventsDisabled]);
};
export default useCreateAndSetDevices;

25
hooks/useFetchPoiData.js Normal file
View File

@@ -0,0 +1,25 @@
// hooks/useFetchPoiData.js
import { useState, useEffect } from "react";
const useFetchPoiData = (url) => {
const [poiData, setPoiData] = useState([]);
useEffect(() => {
const fetchPoiData = async () => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Network response was not ok");
const data = await response.json();
setPoiData(data);
} catch (error) {
console.error("Fehler beim Abrufen der poiData:", error);
}
};
fetchPoiData();
}, [url]);
return poiData;
};
export default useFetchPoiData;

View File

@@ -0,0 +1,26 @@
// hooks/useLayerVisibility.js
import { useEffect } from "react";
import { addContextMenuToMarker } from "../utils/addContextMenuToMarker";
const useLayerVisibility = (map, markers, mapLayersVisibility, layerKey, oms) => {
useEffect(() => {
if (!map || !markers || !oms) return;
const toggleLayer = (isVisible) => {
markers.forEach((marker) => {
if (isVisible) {
marker.addTo(map);
oms.addMarker(marker);
addContextMenuToMarker(marker); // Kontextmenü hinzufügen
} else {
map.removeLayer(marker);
oms.removeMarker(marker);
}
});
};
toggleLayer(mapLayersVisibility[layerKey]);
}, [map, markers, mapLayersVisibility, layerKey, oms]);
};
export default useLayerVisibility;

124
hooks/useLineData-back.js Normal file
View File

@@ -0,0 +1,124 @@
import { useEffect, useState } from "react";
import { SERVER_URL } from "../config/urls";
import { useDispatch, useSelector } from "react-redux";
const useLineData = (webserviceGisLinesStatusUrl, setLineStatusData) => {
const dispatch = useDispatch();
const messages = useSelector((state) => state.messages);
const [lineColors, setLineColors] = useState({});
const [tooltipContents, setTooltipContents] = useState({});
useEffect(() => {
let isCancelled = false;
const fetchData = async () => {
try {
const response1 = await fetch(webserviceGisLinesStatusUrl);
const data1 = await response1.json();
const response2 = await fetch(`${SERVER_URL}:3000/api/talas_v5_DB/gisLines/readGisLines`);
const data2 = await response2.json();
const response3 = await fetch(`${SERVER_URL}:3000/api/talas_v5_DB/device/getAllStationsNames`);
const namesData = await response3.json();
if (!isCancelled) {
const colorsByModule = {};
const newTooltipContents = {};
const valueMap = {};
const sortedStatis = [...data1.Statis].sort((a, b) => a.Level - b.Level);
sortedStatis.forEach((statis) => {
const key = `${statis.IdLD}-${statis.Modul}`;
if (!valueMap[key]) {
valueMap[key] = {
messages: [],
messwert: undefined,
schleifenwert: undefined,
};
}
if (statis.DpName.endsWith("_Messwert") && statis.Value !== "True" && !valueMap[key].messwert) {
valueMap[key].messwert = statis.Value;
}
if (statis.DpName.endsWith("_Schleifenwert") && !valueMap[key].schleifenwert) {
valueMap[key].schleifenwert = statis.Value;
}
if (statis.Message && statis.Message !== "?") {
valueMap[key].messages.push({
message: statis.Message,
prioColor: statis.PrioColor && statis.PrioColor !== "#ffffff" ? statis.PrioColor : "green",
});
}
});
sortedStatis.forEach((statis) => {
const key = `${statis.IdLD}-${statis.Modul}`;
const matchingLine = data2.find((item) => item.idLD === statis.IdLD && item.idModul === statis.Modul);
if (matchingLine) {
const values = valueMap[key];
const messageDisplay = values.messages.map((msg) => `<span class="inline-block text-gray-800"><span class="inline-block w-2 h-2 rounded-full mr-2" style="background-color: ${msg.prioColor};"></span>${msg.message}</span><br>`).join("");
const prioNameDisplay = statis.PrioName && statis.PrioName !== "?" ? `(${statis.PrioName})` : "";
colorsByModule[key] = values.messages.length > 0 ? values.messages[0].prioColor : "green";
newTooltipContents[key] = `
<div class="bg-white rounded-lg m-0 p-2 w-[210px]">
<span class="text-lg font-semibold text-gray-900">${statis.ModulName || "Unknown"}</span>
<br>
<span class="text-md font-bold text-gray-800">${statis.ModulTyp || "N/A"}</span>
<br>
<span class="text-md font-bold text-gray-800">Slot: ${statis.Modul || "N/A"}</span>
<br>
<span class="text-md font-bold text-gray-800">Station: ${namesData[matchingLine.idLD] || "N/A"}</span>
<br>
<div style="max-width: 100%; overflow-wrap: break-word; word-break: break-word; white-space: normal;">
${messageDisplay}
</div>
<br>
${values.messwert ? `<span class="inline-block text-gray-800">Messwert: ${values.messwert}</span><br>` : ""}
${values.schleifenwert ? `<span class="inline-block text-gray-800">Schleifenwert: ${values.schleifenwert}</span>` : ""}
</div>
`;
}
});
setLineColors(colorsByModule);
setTooltipContents(newTooltipContents);
setLineStatusData(data1.Statis);
}
} catch (error) {
console.error("Fehler beim Abrufen der Daten:", error);
try {
window.location.reload();
} catch (reloadError) {
console.error("Fehler beim Neuladen der Seite:", reloadError);
}
}
};
const scheduleNextFetch = () => {
if (!isCancelled) {
fetchData();
setTimeout(scheduleNextFetch, 30000);
}
};
fetchData();
scheduleNextFetch();
return () => {
isCancelled = true;
};
}, [webserviceGisLinesStatusUrl, setLineStatusData]);
return { lineColors, tooltipContents };
};
export default useLineData;

133
hooks/useLineData.js Normal file
View File

@@ -0,0 +1,133 @@
// hooks/useLineData.js
import { useEffect, useState } from "react";
import { SERVER_URL } from "../config/urls";
import { useDispatch, useSelector } from "react-redux";
const useLineData = (webserviceGisLinesStatusUrl, setLineStatusData) => {
const dispatch = useDispatch();
const messages = useSelector((state) => state.messages);
const [lineColors, setLineColors] = useState({});
const [tooltipContents, setTooltipContents] = useState({});
useEffect(() => {
let isCancelled = false;
const fetchData = async () => {
try {
const response1 = await fetch(webserviceGisLinesStatusUrl);
const data1 = await response1.json();
const response2 = await fetch(`${SERVER_URL}:3000/api/talas_v5_DB/gisLines/readGisLines`);
const data2 = await response2.json();
const response3 = await fetch(`${SERVER_URL}:3000/api/talas_v5_DB/device/getAllStationsNames`);
const namesData = await response3.json();
if (!isCancelled) {
const colorsByModule = {};
const newTooltipContents = {};
const valueMap = {};
const sortedStatis = [...data1.Statis].sort((a, b) => a.Level - b.Level);
sortedStatis.forEach((statis) => {
const key = `${statis.IdLD}-${statis.Modul}`;
if (!valueMap[key]) {
valueMap[key] = {
messages: [],
messwert: undefined,
schleifenwert: undefined,
};
}
if (statis.DpName.endsWith("_Messwert") && statis.Value !== "True" && !valueMap[key].messwert) {
valueMap[key].messwert = statis.Value;
}
if (statis.DpName.endsWith("_Schleifenwert") && !valueMap[key].schleifenwert) {
valueMap[key].schleifenwert = statis.Value;
}
if (statis.Message && statis.Message !== "?") {
valueMap[key].messages.push({
message: statis.Message,
prioColor: statis.PrioColor && statis.PrioColor !== "#ffffff" ? statis.PrioColor : "green",
});
}
});
sortedStatis.forEach((statis) => {
const key = `${statis.IdLD}-${statis.Modul}`;
const matchingLine = data2.find((item) => item.idLD === statis.IdLD && item.idModul === statis.Modul);
if (matchingLine) {
const values = valueMap[key];
const messageDisplay = values.messages.map((msg) => `<span class="inline-block text-gray-800"><span class="inline-block w-2 h-2 rounded-full mr-2" style="background-color: ${msg.prioColor};"></span>${msg.message}</span><br>`).join("");
const prioNameDisplay = statis.PrioName && statis.PrioName !== "?" ? `(${statis.PrioName})` : "";
colorsByModule[key] = values.messages.length > 0 ? values.messages[0].prioColor : "green";
newTooltipContents[key] = `
<div class="bg-white rounded-lg m-0 p-2 w-[210px]">
<span class="text-lg font-semibold text-gray-900">${statis.ModulName || "Unknown"}</span>
<br>
<span class="text-md font-bold text-gray-800">${statis.ModulTyp || "N/A"}</span>
<br>
<span class="text-md font-bold text-gray-800">Slot: ${statis.Modul || "N/A"}</span>
<br>
<span class="text-md font-bold text-gray-800">Station: ${namesData[matchingLine.idLD] || "N/A"}</span>
<br>
<div style="max-width: 100%; overflow-wrap: break-word; word-break: break-word; white-space: normal;">
${messageDisplay}
</div>
<br>
${values.messwert ? `<span class="inline-block text-gray-800">Messwert: ${values.messwert}</span><br>` : ""}
${values.schleifenwert ? `<span class="inline-block text-gray-800">Schleifenwert: ${values.schleifenwert}</span>` : ""}
</div>
`;
}
});
setLineColors(colorsByModule);
setTooltipContents(newTooltipContents);
setLineStatusData(data1.Statis);
// Setze den Timeout für die Kontextmenü-Überwachung
const countdownDuration = 30000; // 30 Sekunden für den Countdown
setTimeout(() => {
console.log("Setting contextMenuExpired to true");
localStorage.setItem("contextMenuExpired", "true");
}, countdownDuration);
}
} catch (error) {
console.error("Fehler beim Abrufen der Daten:", error);
try {
window.location.reload();
} catch (reloadError) {
console.error("Fehler beim Neuladen der Seite:", reloadError);
}
}
};
const scheduleNextFetch = () => {
if (!isCancelled) {
fetchData();
setTimeout(scheduleNextFetch, 30000);
}
};
fetchData();
scheduleNextFetch();
return () => {
isCancelled = true;
localStorage.removeItem("contextMenuExpired"); // Flagge entfernen, wenn das Hook unmounted wird
};
}, [webserviceGisLinesStatusUrl, setLineStatusData]);
return { lineColors, tooltipContents };
};
export default useLineData;

View File

@@ -0,0 +1,48 @@
// hooks/useMapComponentState.js
import { useState, useEffect } from "react";
import usePoiTypData from "./usePoiTypData";
import { useRecoilValue } from "recoil";
import { poiLayerVisibleState } from "../store/atoms/poiLayerVisibleState";
export const useMapComponentState = () => {
const { poiTypData, isPoiTypLoaded } = usePoiTypData("/api/talas_v5_DB/poiTyp/readPoiTyp");
const [deviceName, setDeviceName] = useState("");
const [locationDeviceData, setLocationDeviceData] = useState([]);
const [priorityConfig, setPriorityConfig] = useState([]);
const [menuItemAdded, setMenuItemAdded] = useState(false);
const poiLayerVisible = useRecoilValue(poiLayerVisibleState);
// Fetch devices when the component is mounted
useEffect(() => {
const fetchDeviceData = async () => {
try {
const response = await fetch("/api/talas5/location_device"); // API call to get devices
const data = await response.json();
setLocationDeviceData(data); // Set the device data
// Optional: set a default deviceName if needed
if (data.length > 0) {
setDeviceName(data[0].name); // Set the first device's name
}
} catch (error) {
console.error("Error fetching device data:", error);
}
};
fetchDeviceData();
}, []); // Runs only once when the component is mounted
return {
poiTypData,
isPoiTypLoaded,
deviceName,
setDeviceName,
locationDeviceData,
setLocationDeviceData,
priorityConfig,
setPriorityConfig,
menuItemAdded,
setMenuItemAdded,
poiLayerVisible,
};
};

26
hooks/useMarkerLayers.js Normal file
View File

@@ -0,0 +1,26 @@
// hooks/useMarkerLayers.js
import { useEffect } from "react";
import { useRecoilValue } from "recoil";
import { mapLayersState } from "../store/atoms/mapLayersState";
const useMarkerLayers = (map, markers, layerType) => {
const mapLayersVisibility = useRecoilValue(mapLayersState);
useEffect(() => {
if (!map || !markers) return;
const toggleLayer = (isVisible) => {
markers.forEach((marker) => {
if (isVisible) {
marker.addTo(map);
} else {
map.removeLayer(marker);
}
});
};
toggleLayer(mapLayersVisibility[layerType]);
}, [map, markers, mapLayersVisibility, layerType]);
};
export default useMarkerLayers;

26
hooks/usePoiTypData.js Normal file
View File

@@ -0,0 +1,26 @@
// hooks/usePoiTypData.js
import { useState, useEffect } from "react";
const usePoiTypData = (url) => {
const [poiTypData, setPoiTypData] = useState([]);
const [isPoiTypLoaded, setIsPoiTypLoaded] = useState(false);
useEffect(() => {
const fetchPoiTypData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setPoiTypData(data);
setIsPoiTypLoaded(true);
} catch (error) {
console.error("Fehler beim Abrufen der poiTyp-Daten:", error);
}
};
fetchPoiTypData();
}, [url]);
return { poiTypData, isPoiTypLoaded };
};
export default usePoiTypData;

View File

@@ -1,12 +1,10 @@
module.exports = {
setupFilesAfterEnv: ["<rootDir>/setupTests.js"],
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
testEnvironment: "jest-environment-jsdom",
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/"],
transform: {
"^.+\\.(js|jsx)$": "babel-jest",
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
},
transformIgnorePatterns: [
"/node_modules/(?!(@react-leaflet|react-leaflet|leaflet)/)",
],
testEnvironment: "jsdom",
moduleNameMapper: {
"\\.(css|less|scss|sass)$": "identity-obj-proxy",
},

View File

@@ -1 +1,3 @@
import "@testing-library/jest-dom";
// jest.setup.js
global.fetch = require("jest-fetch-mock");
jest.setMock("node-fetch", fetch);

4501
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,56 @@
{
"dependencies": {
"@heroicons/react": "^2.1.3",
"express": "^4.19.2",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@heroicons/react": "^2.1.5",
"@mui/icons-material": "^6.0.2",
"@reduxjs/toolkit": "^2.2.7",
"autoprefixer": "^10.4.19",
"dotenv": "^16.4.5",
"http-proxy-middleware": "^3.0.0",
"leaflet": "^1.9.4",
"leaflet-contextmenu": "^1.4.0",
"leaflet.smooth_marker_bouncing": "^3.0.3",
"lodash": "^4.17.21",
"leaflet.smooth_marker_bouncing": "^3.1.0",
"mysql": "^2.18.1",
"mysql2": "^3.10.1",
"next": "^14.2.3",
"mysql2": "^3.11.0",
"next": "^14.2.5",
"nextjs-cors": "^2.2.0",
"overlapping-marker-spiderfier-leaflet": "^0.2.7",
"postcss": "^8.4.40",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-leaflet": "^4.2.1",
"react-redux": "^9.1.2",
"react-select": "^5.8.0",
"react-toastify": "^10.0.5",
"recoil": "^0.7.7"
"recoil": "^0.7.7",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"tailwindcss": "^3.4.7",
"ws": "^8.18.0"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"export": "next export",
"test": "jest"
"test": "jest",
"cypress": "cypress open",
"cypress:run": "cypress run"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/preset-env": "^7.24.7",
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.2",
"@babel/preset-react": "^7.24.7",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@types/leaflet": "^1.9.12",
"@types/leaflet-contextmenu": "^1.4.3",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.19",
"@testing-library/user-event": "^14.5.2",
"babel-jest": "^29.7.0",
"cypress": "^13.14.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-css-modules-transform": "^4.4.2",
"jest-environment-jsdom": "^29.7.0",
"postcss": "^8.4.39",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.3"
"jest-fetch-mock": "^3.0.3",
"node-fetch": "^3.3.2",
"raw-loader": "^4.0.2"
}
}

View File

@@ -1,13 +1,17 @@
// Pfad: pages/_app.js
import React from "react";
import { RecoilRoot } from "recoil";
import { Provider } from "react-redux";
import store from "../redux/store";
import "../styles/global.css";
function MyApp({ Component, pageProps }) {
return (
<RecoilRoot>
<Component {...pageProps} />
</RecoilRoot>
<Provider store={store}>
<RecoilRoot>
<Component {...pageProps} />
</RecoilRoot>
</Provider>
);
}

View File

@@ -0,0 +1,20 @@
// pages/api/[...path].js
import { createProxyMiddleware } from "http-proxy-middleware";
//import { SERVER_URL } from "../config/urls.js";
//console.log("SERVER_URL:", SERVER_URL); // Debug-Ausgabe
export default createProxyMiddleware({
//target: "http://192.168.10.58:3001",
// Stationen bekommen
//target: "http://10.10.0.13", // Ziel-URL des Proxys // API Aufruf zum mapGisStationsStaticDistrictUrl, mapGisStationsStatusDistrictUrl, mapGisStationsMeasurementsUrl, mapGisSystemStaticUrl und mapDataIconUrl
target: `${process.env.NEXT_PUBLIC_SERVER_URL}`, //
//target: urls.PROXY_TARGET,
//target: "http://localhost:3000", // Ziel-URL des Proxys
//target: "http://192.168.10.187:3000", // Ziel-URL des Proxys
//target: "http://192.168.10.14",
changeOrigin: true,
pathRewrite: {
"^/api": "/", // Optional: Entfernt /api aus dem Pfad, wenn das Backend dies nicht erfordert
},
logLevel: "debug", // Setzt das Logging-Level auf "debug" für detaillierte Ausgaben
});

View File

@@ -0,0 +1,20 @@
// pages/api/get-talasIP.js
export default function handler(req, res) {
// Der x-forwarded-for Header könnte mehrere IP-Adressen enthalten, getrennt durch Kommas
let clientIp =
req.headers["x-forwarded-for"]?.split(",").map((ip) => ip.trim())[0] ||
req.socket.remoteAddress;
// Entfernen möglicher IPv6 "mapped" IPv4 Adressen
if (clientIp?.includes("::ffff:")) {
clientIp = clientIp.split("::ffff:")[1];
}
// Nur IPv4 Adressen weitergeben, IPv6 Adressen ausschließen
if (clientIp && clientIp.includes(":")) {
clientIp = ""; // Dies setzt die IP auf leer, wenn es sich um eine IPv6-Adresse handelt
}
res.status(200).json({ ip: clientIp });
}

View File

@@ -0,0 +1,34 @@
// /pages/api/gis-proxy.js
// Importieren der erforderlichen Module
import httpProxy from "http-proxy";
import Cookies from "cookies";
// Erstellen eines Proxy-Servers
const proxy = httpProxy.createProxyServer();
export default (req, res) => {
return new Promise((resolve) => {
// CORS-Headers einstellen
res.setHeader("Access-Control-Allow-Credentials", true);
res.setHeader("Access-Control-Allow-Origin", "*");
// Cookies initialisieren
const cookies = new Cookies(req, res);
const targetUrl = `${process.env.NEXT_PUBLIC_SERVER_URL}/talas5/ClientData/WebserviceMap.asmx/GisSystemStatic`;
// Proxy-Konfiguration und Event-Listener
req.on("data", () => {});
req.on("end", () => {
proxy.web(req, res, { target: targetUrl, changeOrigin: true, selfHandleResponse: false }, (e) => {
if (e) {
console.error(e);
res.status(500).json({ error: "Proxy-Fehler", e });
}
resolve();
});
});
// Weiterleitung der Headers
req.headers.cookie = cookies.get("cookie-name") || "";
});
};

View File

@@ -0,0 +1,64 @@
// /pages/api/linesColorApi.js
// http://10.10.0.13/talas5/ClientData/WebServiceMap.asmx/GisStationsStatusDistrict
//In DB gis_lines idLD und idModul anpassen entsprechend
// /pages/api/linesColorApi.js
import NextCors from "nextjs-cors";
export default async function handler(req, res) {
// Run the cors middleware
await NextCors(req, res, {
// Options
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
origin: "*", // Erlauben Sie alle Ursprünge, oder geben Sie spezifische Ursprünge an
optionsSuccessStatus: 200, // Legacy-Browser-Unterstützung für 204
});
const response = {
Name: "Liste aller Statis der Linien",
Zeitstempel: new Date().toISOString(), // Aktuellen Zeitstempel hinzufügen
IdMap: "10",
Statis: [
/* {
IdLD: 50922,
Modul: 1,
DpName: "KUE01_Ausfall",
ModulName: "42 Wippershain Sender",
// ModulTyp: "nicht vorhanden",
ModulTyp: "KÜ705-FO",
Message: "KUEG 01: 42 Wippershain Sender Messwerkausfall kommend",
Level: 4,
PrioColor: "#FFFF00",
PrioName: "system",
Value: "10 MOhm",
},
{
IdLD: 25440,
Modul: 3,
DpName: "KUE03_Ausfall",
ModulName: "42 Solz Sender",
//ModulTyp: "nicht vorhanden",
ModulTyp: "KÜSS V2",
Message: "KUEG 03: 42 Solz Sender Messwerkausfall kommend",
Level: 4,
PrioColor: "#FF0000",
PrioName: "system",
Value: "10 MOhm",
},
{
IdLD: 25440,
Modul: 4,
DpName: "KUE04_Ausfall",
ModulName: "42/13 Bad Hersfeld Gaswerk",
ModulTyp: "Kue705-FO",
Message: "KUEG 04: 42/13 Bad Hersfeld Gaswerk Messwerkausfall kommend",
Level: 4,
PrioColor: "#FF00FF",
PrioName: "system",
Value: "10 MOhm",
}, */
],
};
res.status(200).json(response);
}

View File

@@ -0,0 +1,29 @@
// pages/api/rights.js
export default function handler(req, res) {
const { idMap, idUser } = req.query;
// Beispielhafte Rechte, die je nach idMap und idUser variieren können
const rights = {
'10': [
{ IdRight: 1, Name: "Zugriff auf Dashboard" },
{ IdRight: 56, Name: "Erweiterte Berechtigungen" }
],
'2': [
{ IdRight: 2, Name: "Zugriff auf Einstellungen" }
],
'1': [
{ IdRight: 10, Name: "Admin-Zugriff" },
{ IdRight: 11, Name: "Zugriff auf alle Daten" }
]
};
// Prüfung, ob eine gültige idMap und idUser vorhanden sind
if (rights[idMap] && idUser === '484') {
// Rückgabe der spezifischen Rechte basierend auf der idMap und idUser
res.status(200).json({ Rights: rights[idMap] });
} else {
// Rückgabe leerer Rechte für ungültige idMap oder andere Benutzer
res.status(200).json({ Rights: [] });
}
}

View File

@@ -0,0 +1,40 @@
// pages/api/talas_v5/area.js
// Lesen von talas_v5 MySQL-Datenbank -> area Tabelle enthält DAUZ Geräte
// Wenn gebraucht wird, dann nutzen ansonsten löschen
import mysql from "mysql";
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
};
//console.log("my dbconfig: ", dbConfig);
export default function handler(req, res) {
const connection = mysql.createConnection(dbConfig);
connection.connect((err) => {
if (err) {
console.error("Fehler beim Verbinden:", err.stack);
res.status(500).json({ error: "Verbindungsfehler zur Datenbank" });
return;
}
//console.log("Verbunden als ID", connection.threadId);
//Fehler weil, existiertdie Tabelle auf localhost:3000 nicht
connection.query("SELECT ..., ..., ..., ... FROM ... WHERE ... = ...", (error, results) => {
if (error) {
console.error("Fehler beim Abrufen der API", error);
res.status(500).json({ error: "Fehler bei der Abfrage" });
return;
}
// Wichtig: Senden Sie die Antwort zurück
res.status(200).json(results);
connection.end();
});
});
}

View File

@@ -0,0 +1,42 @@
// Importieren des mysql2 Pakets
import mysql from "mysql2";
// Erstellen eines Pools von Datenbankverbindungen
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
// Ein Hilfsfunktion, um Anfragen zu vereinfachen
function queryDatabase(query, params) {
return new Promise((resolve, reject) => {
pool.query(query, params, (error, results) => {
if (error) {
return reject(error);
}
resolve(results);
});
});
}
// API-Handler
export default async function handler(req, res) {
try {
// Dein SQL-Query und die Parameter
const sql = "SELECT idLD, iddevice, name FROM location_device WHERE iddevice = ?";
const params = [160]; // Beispielparameter
// Ausführen der Datenbankabfrage
const results = await queryDatabase(sql, params);
res.status(200).json(results);
} catch (error) {
console.error("Fehler beim Abrufen der API", error);
res.status(500).json({ error: "Fehler bei der Abfrage" });
}
}

View File

@@ -0,0 +1,116 @@
// /pages/api/talas5/webserviceMap/GisStationsMeasurements.js
const GisStationsMeasurements = {
"Name": "Liste aller Messungen der Geraete",
"Zeitstempel": "2024-05-31T15:25:32.5047629+02:00",
"IdMap": "10",
"Statis": [
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 3,
"Na": "FBT",
"Val": "20.5",
"Unit": "°C",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 10,
"Na": "GT",
"Val": "nicht ermittelbar",
"Unit": "°C",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 2,
"Na": "LT",
"Val": "Datenlücke",
"Unit": "°C",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 6,
"Na": "RLF",
"Val": "100.0",
"Unit": "%",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 11,
"Na": "TPT",
"Val": "8.3",
"Unit": "°C",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 12,
"Na": "TT1",
"Val": "---",
"Unit": "°C",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 16,
"Na": "WFD",
"Val": "0.12",
"Unit": "mm",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 8,
"Na": "WGM",
"Val": "---",
"Unit": "m/s",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
},
{
"IdLD": 50004,
"IdL": 18624,
"IdDP": 9,
"Na": "WGS",
"Val": "---",
"Unit": "m/s",
"Gr": "GMA",
"Area_Name": "Renzenhof (RG)"
}
]
}
// Export an async function handler for the API route.
export default async function handler(req, res) {
// Initialize an empty params object to store query parameters.
const params = {
idMap: req.query.idMap,
};
// Check if the requested ID map and user match certain conditions.
if (params.idMap === '10') {
// If the conditions are met, return the GisStationsMeasurements object with a 200 status code.
res.status(200).json(GisStationsMeasurements);
} else {
// If not, return a 404 error with the message "Not Found".
res.status(404).send('Not Found');
}
};

View File

@@ -0,0 +1,281 @@
// /pages/api/talas5/webserviceMap/GisStationsStaticDistrict.js
const GisStationsStaticDistrict = {
"Name": "Liste aller Geraete einer bestimmten Karte",
"Zeitstempel": "2024-05-31T15:26:56.9235766+02:00",
"IdMap": "10",
"Points": [
{
"LD_Name": "CPL Bentheim",
"IdLD": 50017,
"Device": "CPL V3.5 mit 16 Kü",
"Link": "cpl.aspx?ver=35&kue=16&id=50017",
"Location_Name": "Technikraum",
"Location_Short": "BEHE",
"IdLocation": 17448,
"Area_Name": "Bad-Bentheim",
"Area_Short": "BEHE--00",
"IdArea": 16418,
"X": 51.5728,
"Y": 9.00056,
"Icon": 20,
"System": 1,
"Active": 0
},
{
"LD_Name": "Drucker",
"IdLD": 50084,
"Device": "Basisgerät",
"Link": "basis.aspx?ver=1&id=50084",
"Location_Name": "Technikraum",
"Location_Short": "SLUE",
"IdLocation": 17776,
"Area_Name": "Schlüchtern II",
"Area_Short": "SLUE--00",
"IdArea": 14688,
"X": 53.2455,
"Y": 8.1614,
"Icon": 14,
"System": 200,
"Active": 0
},
{
"LD_Name": "Türkontakt",
"IdLD": 50666,
"Device": "ECI",
"Link": "eci.aspx?ver=1&id=50666",
"Location_Name": "Technikraum",
"Location_Short": "SLUE",
"IdLocation": 17776,
"Area_Name": "Schlüchtern II",
"Area_Short": "SLUE--00",
"IdArea": 14688,
"X": 53.2455,
"Y": 8.1614,
"Icon": 17,
"System": 2,
"Active": 0
},
{
"LD_Name": "Triptis",
"IdLD": 50888,
"Device": "CPL 200",
"Link": "cpl.aspx?ver=30&kue=16&id=50888",
"Location_Name": "Technikraum",
"Location_Short": "SLUE",
"IdLocation": 17776,
"Area_Name": "Schlüchtern II",
"Area_Short": "SLUE--00",
"IdArea": 14688,
"X": 53.2455,
"Y": 8.1614,
"Icon": 20,
"System": 1,
"Active": 0
},
{
"LD_Name": "Rodaborn I",
"IdLD": 50889,
"Device": "cpl.mio V>6",
"Link": "cplmio.aspx?ver=1&id=50889",
"Location_Name": "Technikraum",
"Location_Short": "SLUE",
"IdLocation": 17776,
"Area_Name": "Schlüchtern II",
"Area_Short": "SLUE--00",
"IdArea": 14688,
"X": 53.2455,
"Y": 8.1614,
"Icon": 20,
"System": 1,
"Active": 0
},
{
"LD_Name": "Rodaborn II",
"IdLD": 50900,
"Device": "cpl.mio V>6",
"Link": "cplmio.aspx?ver=1&id=50900",
"Location_Name": "Technikraum",
"Location_Short": "SLUE",
"IdLocation": 17776,
"Area_Name": "Schlüchtern II",
"Area_Short": "SLUE--00",
"IdArea": 14688,
"X": 53.2455,
"Y": 8.1614,
"Icon": 20,
"System": 1,
"Active": 0
},
{
"LD_Name": "Hermsdorf",
"IdLD": 50901,
"Device": "CPL V3.5 mit 24 Kü",
"Link": "cpl.aspx?ver=35&kue=24&id=50901",
"Location_Name": "Technikraum",
"Location_Short": "SLUE",
"IdLocation": 17776,
"Area_Name": "Schlüchtern II",
"Area_Short": "SLUE--00",
"IdArea": 14688,
"X": 53.2455,
"Y": 8.1614,
"Icon": 20,
"System": 1,
"Active": 1
},
{
"LD_Name": "GMA Littwin (TEST)",
"IdLD": 50004,
"Device": "Glättemeldeanlage",
"Link": "gma.aspx?ver=1&id=50004",
"Location_Name": "RG Relaisraum",
"Location_Short": "REZR",
"IdLocation": 18624,
"Area_Name": "Renzenhof (RG)",
"Area_Short": "REZHRG00",
"IdArea": 16570,
"X": 53.246036,
"Y": 8.163293,
"Icon": 1,
"System": 11,
"Active": 0
},
{
"LD_Name": "NRS Testserver",
"IdLD": 50005,
"Device": "Notruf Server",
"Link": "nrs_server.aspx?ver=1&id=50005",
"Location_Name": "(EV Ammersricht BZR REL)",
"Location_Short": "AMME",
"IdLocation": 21118,
"Area_Name": "Ammersricht BZR (FGN)",
"Area_Short": "AMMER--00",
"IdArea": 15958,
"X": 52.52726,
"Y": 12.165488,
"Icon": 19,
"System": 8,
"Active": 0
},
{
"LD_Name": "Gateway 2",
"IdLD": 50007,
"Device": "Notruf Server",
"Link": "nrs_server.aspx?ver=1&id=50007",
"Location_Name": "(EV Ammersricht BZR REL)",
"Location_Short": "AMME",
"IdLocation": 21118,
"Area_Name": "Ammersricht BZR (FGN)",
"Area_Short": "AMMER--00",
"IdArea": 15958,
"X": 52.52726,
"Y": 12.165488,
"Icon": 19,
"System": 8,
"Active": 0
},
{
"LD_Name": "Basisgerät mit SNMP MVP",
"IdLD": 50669,
"Device": "Basisgerät + SNMP",
"Link": "basisSNMP.aspx?&ver=1&id=50669",
"Location_Name": "Mylinghauserstraße Engelbert",
"Location_Short": "G-GEVELSBE-1",
"IdLocation": 24012,
"Area_Name": "Gevelsberg",
"Area_Short": "GMA-GEVELSBE",
"IdArea": 20919,
"X": 51.316799,
"Y": 7.33281,
"Icon": 14,
"System": 200,
"Active": 1
},
{
"LD_Name": "Server 3",
"IdLD": 50009,
"Device": "Notruf Server",
"Link": "nrs_server.aspx?ver=1&id=50009",
"Location_Name": "Militärringstraße Militärringstraße",
"Location_Short": "G-KÃLN-1",
"IdLocation": 24015,
"Area_Name": "Köln",
"Area_Short": "GMA-KÃLN",
"IdArea": 20921,
"X": 50.898399,
"Y": 6.92278,
"Icon": 19,
"System": 8,
"Active": 0
},
{
"LD_Name": "ICL Test 5",
"IdLD": 50054,
"Device": "ICL",
"Link": "icl.aspx?ver=1&id=50054",
"Location_Name": "Recheder Mühlenweg Dortmund-Ems-Kanal",
"Location_Short": "G-OLFEN-SE-1",
"IdLocation": 24022,
"Area_Name": "Olfen-Selm",
"Area_Short": "GMA-OLFEN-SE",
"IdArea": 20926,
"X": 51.702202,
"Y": 7.40822,
"Icon": 23,
"System": 100,
"Active": 0
},
{
"LD_Name": "ICL Test 3",
"IdLD": 50052,
"Device": "ICL",
"Link": "icl.aspx?ver=1&id=50052",
"Location_Name": "Weidenstraße Hestenberg",
"Location_Short": "G-PLETTENB-1",
"IdLocation": 24024,
"Area_Name": "Plettenberg",
"Area_Short": "GMA-PLETTENB",
"IdArea": 20928,
"X": 51.224098,
"Y": 7.86969,
"Icon": 23,
"System": 100,
"Active": 0
},
{
"LD_Name": "Test Februar Kai",
"IdLD": 50912,
"Device": "Dauerzählstelle DZ",
"Link": "dauz.aspx?ver=1&id=50912",
"Location_Name": "In der Hoffnung Kiesberg - BG Ost",
"Location_Short": "G-WUPPERTA-4",
"IdLocation": 24039,
"Area_Name": "Wuppertal",
"Area_Short": "GMA-WUPPERTA",
"IdArea": 20937,
"X": 51.238899,
"Y": 7.12715,
"Icon": 14,
"System": 110,
"Active": 1
}
]
}
// Export an async function handler for the API route.
export default async function handler(req, res) {
// Initialize an empty params object to store query parameters.
const params = {
idMap: req.query.idMap,
idUser: req.query.idUser
};
// Check if the requested ID map and user match certain conditions.
if (params.idMap === '10' && params.idUser === '484') {
// If the conditions are met, return the GisStationsStaticDistrict object with a 200 status code.
res.status(200).json(GisStationsStaticDistrict);
} else {
// If not, return a 404 error with the message "Not Found".
res.status(404).send('Not Found');
}
};

View File

@@ -0,0 +1,84 @@
// /pages/api/talas5/webserviceMap/gisStationsMeasurementsSQL.js
import mysql from "mysql2/promise";
// Erstellen eines Verbindungspools anstelle einer einzelnen Verbindung
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
export default async function handler(req, res) {
const { idMap, idUser } = req.query;
if (!idMap || !idUser) {
return res.status(400).json({ error: "idMap and idUser are required" });
}
try {
let onlySystem = -1;
let districtCounter = 0;
// Get onlySystem
const [mapResult] = await pool.query("SELECT idsystem_typ FROM maps WHERE id = ?", [idMap]);
if (mapResult.length > 0) {
onlySystem = mapResult[0].idsystem_typ ?? -1;
}
// Get districtCounter
if (idUser > 0) {
const [userLayerResult] = await pool.query("SELECT count(*) as count FROM user_User_layer1 WHERE iduser = ?", [idUser]);
districtCounter = userLayerResult[0].count;
}
// Building the query
let query = `
SELECT ld.idLD, dc.message, p.level, p.name, p.color, ld.idDevice, de.isService, dc.idIcon
FROM location as l
LEFT JOIN location_coordinates AS co ON l.idLocation = co.idLocation and co.idMaps = ?
LEFT JOIN location_device AS ld ON ld.idLocation = l.idLocation
LEFT JOIN datapoint as d ON d.idLD = ld.idLD
LEFT JOIN datapoint_conditions AS dc ON dc.idcondition = d.last_message_condition
LEFT JOIN prio AS p ON p.idPrio = dc.idprio
LEFT JOIN devices AS de ON de.idDevice = ld.idDevice
LEFT JOIN area as a on a.idArea = l.idArea
WHERE p.level < 100 AND co.X > 0
`;
const queryParams = [idMap];
if (districtCounter > 0) {
query += ` AND a.iddistrict IN (SELECT iddistrict FROM user_user_layer1 WHERE iduser = ?)`;
queryParams.push(idUser);
}
if (onlySystem >= 0) {
query += ` AND de.idsystem_typ = ?`;
queryParams.push(onlySystem);
}
query += ` ORDER BY p.level desc`;
const [results] = await pool.query(query, queryParams);
const mpss = {
IdMap: idMap.toString(),
Statis: results.map((row) => ({
IdLD: row.idLD ?? -1,
Le: row.level ?? -1,
Me: row.message ?? "?",
Na: row.name ?? "?",
Co: row.color ?? "#ffffff",
Feld: row.idDevice ?? -1,
Icon: row.idIcon ?? 0,
})),
};
res.status(200).json(mpss);
} catch (error) {
console.error("Fehler beim Laden der Daten:", error);
res.status(500).json({ error: "Interner Serverfehler" });
}
}

View File

@@ -0,0 +1,273 @@
// /pages/api/webServiceMap.js
const gisSystemStatic = {
"Name": "Liste aller angezeigten Systeme",
"Zeitstempel": "2024-05-31T15:08:49.8599542+02:00",
"IdMap": "10",
"Systems": [
{
"IdSystem": 1,
"Name": "TALAS",
"Longname": "Talas Meldestationen",
"Allow": 1,
"Icon": 1
},
{
"IdSystem": 2,
"Name": "ECI",
"Longname": "ECI Geräte",
"Allow": 1,
"Icon": 2
},
{
"IdSystem": 5,
"Name": "GSM Modem",
"Longname": "LR77 GSM Modems",
"Allow": 1,
"Icon": 5
},
{
"IdSystem": 6,
"Name": "Cisco Router",
"Longname": "Cisco Router",
"Allow": 1,
"Icon": 6
},
{
"IdSystem": 7,
"Name": "WAGO",
"Longname": "WAGO I/O Systeme",
"Allow": 1,
"Icon": 7
},
{
"IdSystem": 8,
"Name": "Siemens",
"Longname": "Siemens Notrufsystem",
"Allow": 0,
"Icon": 8
},
{
"IdSystem": 9,
"Name": "OTDR",
"Longname": "Glasfaserüberwachung OTU",
"Allow": 0,
"Icon": 9
},
{
"IdSystem": 10,
"Name": "WDM",
"Longname": " Wavelength Division Multiplexing",
"Allow": 0,
"Icon": 10
},
{
"IdSystem": 11,
"Name": "GMA",
"Longname": "Glättemeldeanlagen",
"Allow": 1,
"Icon": 11
},
{
"IdSystem": 13,
"Name": "Messstellen",
"Longname": "Messstellen",
"Allow": 0,
"Icon": 13
},
{
"IdSystem": 100,
"Name": "TALAS ICL",
"Longname": "Talas ICL Unterstationen",
"Allow": 1,
"Icon": 100
},
{
"IdSystem": 110,
"Name": "DAUZ",
"Longname": "Dauerzählstellen",
"Allow": 1,
"Icon": 110
},
{
"IdSystem": 111,
"Name": "SMS-Funkmodem",
"Longname": "SMS-Funkmodem",
"Allow": 0,
"Icon": 111
},
{
"IdSystem": 200,
"Name": "Sonstige",
"Longname": "Sonstige",
"Allow": 1,
"Icon": 200
}
],
"Rights": [
{
"IdRight": 1
},
{
"IdRight": 2
},
{
"IdRight": 3
},
{
"IdRight": 5
},
{
"IdRight": 6
},
{
"IdRight": 7
},
{
"IdRight": 8
},
{
"IdRight": 10
},
{
"IdRight": 11
},
{
"IdRight": 12
},
{
"IdRight": 20
},
{
"IdRight": 22
},
{
"IdRight": 23
},
{
"IdRight": 25
},
{
"IdRight": 30
},
{
"IdRight": 40
},
{
"IdRight": 41
},
{
"IdRight": 42
},
{
"IdRight": 43
},
{
"IdRight": 44
},
{
"IdRight": 45
},
{
"IdRight": 46
},
{
"IdRight": 47
},
{
"IdRight": 48
},
{
"IdRight": 49
},
{
"IdRight": 50
},
{
"IdRight": 51
},
{
"IdRight": 52
},
{
"IdRight": 55
},
{
"IdRight": 56
},
{
"IdRight": 60
},
{
"IdRight": 61
},
{
"IdRight": 62
},
{
"IdRight": 63
},
{
"IdRight": 64
},
{
"IdRight": 65
},
{
"IdRight": 68
},
{
"IdRight": 69
},
{
"IdRight": 70
},
{
"IdRight": 71
},
{
"IdRight": 72
},
{
"IdRight": 73
},
{
"IdRight": 79
},
{
"IdRight": 80
},
{
"IdRight": 90
},
{
"IdRight": 93
},
{
"IdRight": 94
},
{
"IdRight": 95
},
{
"IdRight": 96
}
]
}
// Export an async function handler for the API route.
export default async function handler(req, res) {
// Initialize an empty params object to store query parameters.
const params = {
idMap: req.query.idMap,
idUser: req.query.idUser
};
// Check if the requested ID map and user match certain conditions.
if (params.idMap === '10' && params.idUser === '484') {
// If the conditions are met, return the gisSystemStatic object with a 200 status code.
res.status(200).json(gisSystemStatic);
} else {
// If not, return a 404 error with the message "Not Found".
res.status(404).send('Not Found');
}
};

View File

@@ -0,0 +1,74 @@
// /pages/api/talas5/webserviceMap/gisStationsMeasurementsSQL.js
import mysql from "mysql2";
// Erstellen eines Pools von Datenbankverbindungen
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
pool.on("connection", function (connection) {
console.log("Database connected successfully.");
});
pool.on("error", function (err) {
console.error("Fehler beim Verbinden:", err);
});
export default function handler(req, res) {
const idMap = req.query.idMap;
if (req.method !== "GET") {
return res.status(405).json({ error: "Nur GET Methode erlaubt" });
}
const sqlQuery = `
SELECT
ld.idLD,
dp.idDP,
dp.name AS Na,
dp.value AS Val,
dp.unit AS Unit,
mg.name AS Gr,
ld.idLocation,
area.Name AS Area_Name
FROM location_device as ld
LEFT JOIN location_coordinates AS co ON ld.idLocation = co.idLocation and co.idMaps = ?
LEFT JOIN devices AS de ON de.idDevice = ld.idDevice
LEFT JOIN datapoint AS dp ON ld.idLD = dp.idLD
LEFT JOIN message_group AS mg ON dp.idmessage_group = mg.idmessage_group
LEFT JOIN location AS loc ON ld.idLocation = loc.idLocation
LEFT JOIN area AS area ON loc.idArea = area.idArea
WHERE co.X > 0 AND dp.idmessage_group>0 AND length(dp.unit)> 0 AND length(dp.value)> 0
`;
pool.query(sqlQuery, [idMap], (error, results) => {
if (error) {
console.error("Fehler beim Abrufen der gis_lines:", error);
return res.status(500).json({ error: "Fehler beim Abrufen der gis_lines" });
}
const response = {
Name: "Liste aller Messungen der Geraete",
Zeitstempel: new Date().toISOString(),
IdMap: idMap,
Statis: results.map((row) => ({
IdLD: row.idLD,
IdDP: row.idDP,
Na: row.Na,
Val: row.Val,
Unit: row.Unit,
Gr: row.Gr,
IdLocation: row.idLocation,
Area_Name: row.Area_Name,
})),
};
res.json(response);
});
}

View File

@@ -0,0 +1,34 @@
// /pages/api/talas_v5_DB/gisLines/readGisLines.js
import mysql from "mysql2/promise";
// Erstellen eines Pools von Datenbankverbindungen
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
export default async function handler(req, res) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Nur GET Methode erlaubt" });
}
const query = "SELECT * FROM talas_v5.gis_lines;";
try {
const [results] = await pool.query(query);
if (results.length > 0) {
res.status(200).json(results);
} else {
res.status(404).json({ error: "Gerät nicht gefunden" });
}
} catch (error) {
console.error("Fehler beim Abrufen der gis_lines:", error);
res.status(500).json({ error: "Fehler beim Abrufen der gis_lines" });
}
}

View File

@@ -0,0 +1,58 @@
import mysql from "mysql2/promise";
// Erstellen eines Pools von Datenbankverbindungen
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
export default async function handler(req, res) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Nur POST Methode erlaubt" });
}
const { idLD, idModul, newCoordinates } = req.body;
if (!idLD || !idModul || !newCoordinates) {
return res.status(400).json({ error: "Fehlende Daten" });
}
const newLineString = `LINESTRING(${newCoordinates.map((coord) => `${coord[0]} ${coord[1]}`).join(",")})`;
const query = "UPDATE talas_v5.gis_lines SET points = ST_GeomFromText(?) WHERE idLD = ? AND idModul = ?;";
let connection;
try {
// Hole eine Verbindung aus dem Pool
connection = await pool.getConnection();
// Beginne eine Transaktion
await connection.beginTransaction();
// Führe die Abfrage aus
const [results] = await connection.query(query, [newLineString, idLD, idModul]);
// Commit der Transaktion
await connection.commit();
console.log("Transaction Complete.");
res.status(200).json({
success: "Updated successfully.",
affectedRows: results.affectedRows,
});
} catch (error) {
// Rollback im Falle eines Fehlers
if (connection) await connection.rollback();
console.error("Fehler beim Aktualisieren der gis_lines:", error);
res.status(500).json({ error: "Fehler beim Aktualisieren der gis_lines" });
} finally {
// Stelle sicher, dass die Verbindung zurückgegeben wird
if (connection) connection.release();
}
}

View File

@@ -0,0 +1,41 @@
// API in /api/talas_v5_DB/locationDevice/getDeviceId.js
import mysql from "mysql2/promise";
// Erstellen eines Pools von Datenbankverbindungen
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
export default async function handler(req, res) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Nur GET Methode erlaubt" });
}
const { deviceName } = req.query;
if (!deviceName) {
return res.status(400).json({ error: "deviceName ist erforderlich" });
}
const query = "SELECT idLD FROM location_device WHERE name = ?";
try {
// Ausführen der Abfrage mit dem Pool
const [results] = await pool.query(query, [deviceName]);
if (results.length > 0) {
res.status(200).json({ idLD: results[0].idLD });
} else {
res.status(404).json({ error: "Gerät nicht gefunden" });
}
} catch (error) {
console.error("Fehler beim Abrufen der Geräte-ID:", error);
res.status(500).json({ error: "Fehler beim Abrufen der Geräte-ID" });
}
}

View File

@@ -0,0 +1,40 @@
// API in /api/talas_v5_DB/locationDevice/locationDeviceNameById.js
import mysql from "mysql2/promise";
// Erstellen eines Pools von Datenbankverbindungen
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
export default async function handler(req, res) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Nur GET Methode erlaubt" });
}
const { idLD } = req.query;
if (!idLD) {
return res.status(400).json({ error: "idLD ist erforderlich" });
}
try {
const query = "SELECT name FROM location_device WHERE idLD = ?";
const [results] = await pool.query(query, [idLD]);
if (results.length > 0) {
res.status(200).json({ name: results[0].name });
} else {
res.status(404).json({ error: "Gerät nicht gefunden", idLD });
}
} catch (error) {
console.error("Fehler beim Abrufen des Gerätenamens:", error);
res.status(500).json({ error: "Fehler beim Abrufen des Gerätenamens" });
}
}

View File

@@ -0,0 +1,32 @@
// API in /api/talas_v5_DB/locationDevice/locationDevices.js
import mysql from "mysql2/promise";
// Erstellen eines Pools von Datenbankverbindungen
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
export default async function handler(req, res) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Nur GET Methode erlaubt" });
}
const query = "SELECT * FROM location_device WHERE iddevice = 160";
try {
// Ausführen der Abfrage mit dem Verbindungspool
const [results] = await pool.query(query);
res.status(200).json(results);
} catch (error) {
console.error("Fehler beim Abrufen der Geräteinformationen:", error);
res.status(500).json({ error: "Fehler beim Abrufen der Geräteinformationen" });
}
}

Some files were not shown because too many files have changed in this diff Show More