fix: Gerätemarker-Sichtbarkeit an Redux-Layerzustand gekoppelt
- Hardcodiertes Zeichnen der Gerätemarker beim Initialisieren entfernt - Sichtbarkeitssteuerung vollständig über mapLayersVisibility aus Redux umgesetzt - Dynamische Layererzeugung aus GisSystemStatic integriert - Marker werden nur angezeigt, wenn zugehöriger Layer aktiv ist
This commit is contained in:
@@ -41,13 +41,25 @@ import { selectGisLines } from "../../redux/slices/database/polylines/gisLinesSl
|
||||
import { selectGisLinesStatus } from "../../redux/slices/webservice/gisLinesStatusSlice";
|
||||
import { selectPoiTypData, selectPoiTypStatus } from "../../redux/slices/database/pois/poiTypSlice";
|
||||
import { selectPriorityConfig } from "../../redux/slices/database/priorityConfigSlice.js";
|
||||
import { selectPoiIconsData, selectPoiIconsStatus } from "../../redux/slices/database/pois/poiIconsDataSlice";
|
||||
import {
|
||||
selectPoiIconsData,
|
||||
selectPoiIconsStatus,
|
||||
} from "../../redux/slices/database/pois/poiIconsDataSlice";
|
||||
import { selectGisLinesStatusFromWebservice } from "../../redux/slices/webservice/gisLinesStatusSlice";
|
||||
import { selectGisUserRightsFromWebservice } from "../../redux/slices/webservice/userRightsSlice";
|
||||
import { updateCountdown, closePolylineContextMenu } from "../../redux/slices/database/polylines/polylineContextMenuSlice.js";
|
||||
import { selectPolylineVisible, setPolylineVisible } from "../../redux/slices/database/polylines/polylineLayerVisibleSlice.js";
|
||||
import {
|
||||
updateCountdown,
|
||||
closePolylineContextMenu,
|
||||
} from "../../redux/slices/database/polylines/polylineContextMenuSlice.js";
|
||||
import {
|
||||
selectPolylineVisible,
|
||||
setPolylineVisible,
|
||||
} from "../../redux/slices/database/polylines/polylineLayerVisibleSlice.js";
|
||||
import { selectGisStationsStaticDistrict } from "../../redux/slices/webservice/gisStationsStaticDistrictSlice.js";
|
||||
import { selectGisSystemStatic, setGisSystemStatic } from "../../redux/slices/webservice/gisSystemStaticSlice.js";
|
||||
import {
|
||||
selectGisSystemStatic,
|
||||
setGisSystemStatic,
|
||||
} from "../../redux/slices/webservice/gisSystemStaticSlice.js";
|
||||
//-----------Redux-Thunks-------------------
|
||||
import { fetchGisStationsMeasurementsThunk } from "../../redux/thunks/webservice/fetchGisStationsMeasurementsThunk";
|
||||
import { fetchGisSystemStaticThunk } from "../../redux/thunks/webservice/fetchGisSystemStaticThunk";
|
||||
@@ -63,38 +75,43 @@ import { fetchPoiTypThunk } from "../../redux/thunks/database/pois/fetchPoiTypTh
|
||||
import { updateAreaThunk } from "../../redux/thunks/database/area/updateAreaThunk";
|
||||
|
||||
import useDynamicDeviceLayers from "../../hooks/layers/useDynamicDeviceLayers";
|
||||
|
||||
import useDataUpdater from "@/hooks/useDataUpdater";
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
//-------------------------------
|
||||
const dispatch = useDispatch();
|
||||
useDataUpdater();
|
||||
|
||||
const countdown = useSelector((state) => state.polylineContextMenu.countdown);
|
||||
const countdownActive = useSelector((state) => state.polylineContextMenu.countdownActive);
|
||||
const isPolylineContextMenuOpen = useSelector((state) => state.polylineContextMenu.isOpen);
|
||||
const countdown = useSelector(state => state.polylineContextMenu.countdown);
|
||||
const countdownActive = useSelector(state => state.polylineContextMenu.countdownActive);
|
||||
const isPolylineContextMenuOpen = useSelector(state => state.polylineContextMenu.isOpen);
|
||||
const polylineVisible = useSelector(selectPolylineVisible);
|
||||
|
||||
const isPoiTypLoaded = useSelector((state) => state.poiTypes.status === "succeeded");
|
||||
const statusMeasurements = useSelector((state) => state.gisStationsMeasurements.status);
|
||||
const statusSystem = useSelector((state) => state.gisSystemStatic.status);
|
||||
const statusStaticDistrict = useSelector((state) => state.gisStationsStaticDistrict.status);
|
||||
const statusStatusDistrict = useSelector((state) => state.gisStationsStatusDistrict.status);
|
||||
const isPoiTypLoaded = useSelector(state => state.poiTypes.status === "succeeded");
|
||||
const statusMeasurements = useSelector(state => state.gisStationsMeasurements.status);
|
||||
const statusSystem = useSelector(state => state.gisSystemStatic.status);
|
||||
const statusStaticDistrict = useSelector(state => state.gisStationsStaticDistrict.status);
|
||||
const statusStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.status);
|
||||
const priorityConfig = useSelector(selectPriorityConfig);
|
||||
const poiLayerVisible = useSelector((state) => state.poiLayerVisible.visible);
|
||||
const zoomTrigger = useSelector((state) => state.zoomTrigger.trigger);
|
||||
const poiReadTrigger = useSelector((state) => state.poiReadFromDbTrigger.trigger);
|
||||
const poiLayerVisible = useSelector(state => state.poiLayerVisible.visible);
|
||||
const zoomTrigger = useSelector(state => state.zoomTrigger.trigger);
|
||||
const poiReadTrigger = useSelector(state => state.poiReadFromDbTrigger.trigger);
|
||||
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict);
|
||||
const GisSystemStatic = useSelector(selectGisSystemStatic);
|
||||
const gisSystemStaticStatus = useSelector((state) => state.gisSystemStatic.status);
|
||||
const polylineEventsDisabled = useSelector((state) => state.polylineEventsDisabled.disabled);
|
||||
const gisSystemStaticStatus = useSelector(state => state.gisSystemStatic.status);
|
||||
const polylineEventsDisabled = useSelector(state => state.polylineEventsDisabled.disabled);
|
||||
const mapLayersVisibility = useSelector(selectMapLayersState) || {};
|
||||
const selectedArea = useSelector((state) => state.selectedArea.area);
|
||||
const linesData = useSelector((state) => state.gisLinesFromDatabase.data);
|
||||
const gisLinesStatus = useSelector((state) => state.gisLinesStatusFromWebservice.status);
|
||||
const { data: gisLinesStatusData, status: statusGisLinesStatus } = useSelector(selectGisLinesStatusFromWebservice);
|
||||
const selectedArea = useSelector(state => state.selectedArea.area);
|
||||
const linesData = useSelector(state => state.gisLinesFromDatabase.data);
|
||||
const gisLinesStatus = useSelector(state => state.gisLinesStatusFromWebservice.status);
|
||||
const { data: gisLinesStatusData, status: statusGisLinesStatus } = useSelector(
|
||||
selectGisLinesStatusFromWebservice
|
||||
);
|
||||
const poiIconsData = useSelector(selectPoiIconsData);
|
||||
const poiIconsStatus = useSelector(selectPoiIconsStatus);
|
||||
const poiTypData = useSelector(selectPoiTypData);
|
||||
const poiTypStatus = useSelector((state) => state.poiTypes.status);
|
||||
const poiTypStatus = useSelector(state => state.poiTypes.status);
|
||||
//const poiTypStatus = useSelector(selectPoiTypStatus);
|
||||
|
||||
//-------------------------------
|
||||
@@ -115,11 +132,13 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
const [oms, setOms] = useState(null); // State für OMS-Instanz
|
||||
|
||||
//-----userRights----------------
|
||||
const isRightsLoaded = useSelector((state) => state.gisUserRightsFromWebservice.status === "succeeded");
|
||||
const isRightsLoaded = useSelector(
|
||||
state => state.gisUserRightsFromWebservice.status === "succeeded"
|
||||
);
|
||||
const userRights = useSelector(selectGisUserRightsFromWebservice);
|
||||
const hasRights = userRights.includes(56);
|
||||
//-----------------------------
|
||||
const openPopupWithCoordinates = (e) => {
|
||||
const openPopupWithCoordinates = e => {
|
||||
const coordinates = `${e.latlng.lat.toFixed(5)}, ${e.latlng.lng.toFixed(5)}`;
|
||||
setCurrentCoordinates(coordinates);
|
||||
setIsPopupOpen(true);
|
||||
@@ -166,20 +185,29 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
|
||||
//--------------------------------------------
|
||||
|
||||
const handleCoordinatesSubmit = (coords) => {
|
||||
const handleCoordinatesSubmit = coords => {
|
||||
const [lat, lng] = coords.split(",").map(Number);
|
||||
if (map && lat && lng) {
|
||||
map.flyTo([lat, lng], 12); // Zentriere die Karte auf die Koordinaten
|
||||
}
|
||||
};
|
||||
//-----------------------------Map Initialisierung----------------
|
||||
useInitializeMap(map, mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, (value) => dispatch(setDisabled(value)));
|
||||
useInitializeMap(
|
||||
map,
|
||||
mapRef,
|
||||
setMap,
|
||||
setOms,
|
||||
setMenuItemAdded,
|
||||
addItemsToMapContextMenu,
|
||||
hasRights,
|
||||
value => dispatch(setDisabled(value))
|
||||
);
|
||||
|
||||
//-------------------------React Hooks--------------------------------
|
||||
useEffect(() => {
|
||||
if (linesData && Array.isArray(linesData)) {
|
||||
const transformed = linesData.map((item) => ({
|
||||
coordinates: item.points.map((point) => [point.x, point.y]),
|
||||
const transformed = linesData.map(item => ({
|
||||
coordinates: item.points.map(point => [point.x, point.y]),
|
||||
idModul: item.idModul,
|
||||
idLD: item.idLD,
|
||||
}));
|
||||
@@ -204,7 +232,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
map.removeLayer(poiLayerRef.current);
|
||||
poiLayerRef.current = new L.LayerGroup().addTo(map);
|
||||
}
|
||||
locations.forEach((location) => {});
|
||||
locations.forEach(location => {});
|
||||
};
|
||||
//console.log("trigger in MapComponent.js:", poiReadTrigger);
|
||||
}, [map, locations, poiReadTrigger]);
|
||||
@@ -233,7 +261,18 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
deviceName,
|
||||
dispatch
|
||||
);
|
||||
}, [map, locations, onLocationUpdate, poiReadTrigger, isPoiTypLoaded, userRights, poiLayerVisible, poiData, poiTypMap, dispatch]);
|
||||
}, [
|
||||
map,
|
||||
locations,
|
||||
onLocationUpdate,
|
||||
poiReadTrigger,
|
||||
isPoiTypLoaded,
|
||||
userRights,
|
||||
poiLayerVisible,
|
||||
poiData,
|
||||
poiTypMap,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
//---------------------------------------------
|
||||
//console.log("priorityConfig in MapComponent2: ", priorityConfig);
|
||||
@@ -263,8 +302,8 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
if (!map) return;
|
||||
|
||||
// Entferne alte Marker und Polylinien
|
||||
markers.forEach((marker) => marker.remove());
|
||||
polylines.forEach((polyline) => polyline.remove());
|
||||
markers.forEach(marker => marker.remove());
|
||||
polylines.forEach(polyline => polyline.remove());
|
||||
|
||||
// Setze neue Marker und Polylinien mit den aktuellen Daten
|
||||
const { markers: newMarkers, polylines: newPolylines } = setupPolylines(
|
||||
@@ -281,7 +320,9 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
|
||||
newPolylines.forEach((polyline, index) => {
|
||||
//console.log("polyline: ", polyline);
|
||||
const tooltipContent = tooltipContents[`${linePositions[index].idLD}-${linePositions[index].idModul}`] || "Die Linie ist noch nicht in Webservice vorhanden oder bekommt keine Daten";
|
||||
const tooltipContent =
|
||||
tooltipContents[`${linePositions[index].idLD}-${linePositions[index].idModul}`] ||
|
||||
"Die Linie ist noch nicht in Webservice vorhanden oder bekommt keine Daten";
|
||||
|
||||
polyline.bindTooltip(tooltipContent, {
|
||||
permanent: false,
|
||||
@@ -291,7 +332,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
pane: "tooltipPane",
|
||||
});
|
||||
|
||||
polyline.on("mouseover", (e) => {
|
||||
polyline.on("mouseover", e => {
|
||||
const tooltip = polyline.getTooltip();
|
||||
if (tooltip) {
|
||||
const mousePos = e.containerPoint;
|
||||
@@ -323,7 +364,16 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
|
||||
setMarkers(newMarkers);
|
||||
setPolylines(newPolylines);
|
||||
}, [map, linePositions, lineColors, tooltipContents, newPoint, newCoords, tempMarker, polylineVisible]);
|
||||
}, [
|
||||
map,
|
||||
linePositions,
|
||||
lineColors,
|
||||
tooltipContents,
|
||||
newPoint,
|
||||
newCoords,
|
||||
tempMarker,
|
||||
polylineVisible,
|
||||
]);
|
||||
|
||||
//--------------------------------------------
|
||||
|
||||
@@ -338,7 +388,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
console.log("map in MapComponent: ", map);
|
||||
const handleMapMoveEnd = (event) => {
|
||||
const handleMapMoveEnd = event => {
|
||||
const newCenter = map.getCenter();
|
||||
const newZoom = map.getZoom();
|
||||
|
||||
@@ -367,7 +417,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
const points = GisStationsStaticDistrict?.Points;
|
||||
|
||||
if (selectedArea && map) {
|
||||
const station = points.find((s) => s.Area_Name === selectedArea);
|
||||
const station = points.find(s => s.Area_Name === selectedArea);
|
||||
|
||||
if (station) {
|
||||
console.log("📌 Gefundene Station:", station);
|
||||
@@ -387,7 +437,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
//--------------------------------------------
|
||||
useEffect(() => {
|
||||
if (map && poiLayerRef.current && isPoiTypLoaded && !menuItemAdded && isRightsLoaded) {
|
||||
addItemsToMapContextMenu(map, menuItemAdded, setMenuItemAdded, hasRights, setShowPopup, setPopupCoordinates);
|
||||
addItemsToMapContextMenu(
|
||||
map,
|
||||
menuItemAdded,
|
||||
setMenuItemAdded,
|
||||
hasRights,
|
||||
setShowPopup,
|
||||
setPopupCoordinates
|
||||
);
|
||||
}
|
||||
}, [map, poiLayerRef, isPoiTypLoaded, menuItemAdded, hasRights, isRightsLoaded]);
|
||||
//--------------------------------------------
|
||||
@@ -464,14 +521,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
// Entferne alle Marker aus der Karte
|
||||
if (!map) return; // Sicherstellen, dass map existiert
|
||||
|
||||
areaMarkers.forEach((marker) => {
|
||||
areaMarkers.forEach(marker => {
|
||||
if (map.hasLayer(marker)) {
|
||||
map.removeLayer(marker);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Wenn editMode aktiviert ist, füge die Marker hinzu und aktiviere Dragging
|
||||
areaMarkers.forEach((marker) => {
|
||||
areaMarkers.forEach(marker => {
|
||||
if (!map.hasLayer(marker)) {
|
||||
marker.addTo(map); // Layer hinzufügen
|
||||
}
|
||||
@@ -482,7 +539,13 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
}, [areaMarkers, map]);
|
||||
|
||||
//----------------------------------
|
||||
const { markerStates, layerRefs } = useDynamicDeviceLayers(map, GisSystemStatic, mapLayersVisibility, priorityConfig, oms);
|
||||
const { markerStates, layerRefs } = useDynamicDeviceLayers(
|
||||
map,
|
||||
GisSystemStatic,
|
||||
mapLayersVisibility,
|
||||
priorityConfig,
|
||||
oms
|
||||
);
|
||||
useEffect(() => {
|
||||
const handleVisibilityChange = () => {
|
||||
if (!map) return;
|
||||
@@ -584,7 +647,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
dispatch(fetchGisLinesStatusThunk());
|
||||
}, [dispatch]);
|
||||
//---------------------------------------------------------
|
||||
const rights = useSelector((state) => state.gisUserRightsFromWebservice.rights);
|
||||
const rights = useSelector(state => state.gisUserRightsFromWebservice.rights);
|
||||
useEffect(() => {
|
||||
dispatch(fetchUserRightsThunk());
|
||||
}, [dispatch]);
|
||||
@@ -653,7 +716,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
useEffect(() => {
|
||||
if (poiTypStatus === "succeeded" && Array.isArray(poiTypData)) {
|
||||
const map = new Map();
|
||||
poiTypData.forEach((item) => map.set(item.idPoiTyp, item.name));
|
||||
poiTypData.forEach(item => map.set(item.idPoiTyp, item.name));
|
||||
setPoiTypMap(map);
|
||||
}
|
||||
}, [poiTypData, poiTypStatus]);
|
||||
@@ -672,7 +735,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
|
||||
Object.entries(markerStates).forEach(([systemName, markers]) => {
|
||||
const isVisible = mapLayersVisibility[systemName];
|
||||
markers.forEach((marker) => {
|
||||
markers.forEach(marker => {
|
||||
const hasLayer = map.hasLayer(marker);
|
||||
if (editMode || !isVisible) {
|
||||
if (hasLayer) map.removeLayer(marker);
|
||||
@@ -684,7 +747,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
|
||||
// optional für alle zusammen
|
||||
const allMarkers = Object.values(markerStates)
|
||||
.filter((entry) => Array.isArray(entry))
|
||||
.filter(entry => Array.isArray(entry))
|
||||
.flat();
|
||||
|
||||
checkOverlappingMarkers(map, allMarkers, plusRoundIcon);
|
||||
@@ -697,22 +760,55 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
window.oms = oms; // Für Debugging global
|
||||
}
|
||||
}, [oms]);
|
||||
|
||||
//---------------------------------------------
|
||||
//--------------------------------------------
|
||||
return (
|
||||
<>
|
||||
{/* Zeigt das POI-Modal, wenn `showPoiModal` true ist */}
|
||||
{showPoiModal && <AddPOIModal latlng={popupCoordinates} onClose={() => setShowPoiModal(false)} />}
|
||||
{showPoiModal && (
|
||||
<AddPOIModal latlng={popupCoordinates} onClose={() => setShowPoiModal(false)} />
|
||||
)}
|
||||
<ToastContainer />
|
||||
<div>{showPoiUpdateModal && <PoiUpdateModal onClose={() => setShowPoiUpdateModal(false)} poiData={currentPoiData} onSubmit={() => {}} latlng={popupCoordinates} />}</div>
|
||||
<div>
|
||||
{showPoiUpdateModal && (
|
||||
<PoiUpdateModal
|
||||
onClose={() => setShowPoiUpdateModal(false)}
|
||||
poiData={currentPoiData}
|
||||
onSubmit={() => {}}
|
||||
latlng={popupCoordinates}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{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" />
|
||||
<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>
|
||||
</div>
|
||||
@@ -720,7 +816,9 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{GisStationsStaticDistrict && GisStationsStaticDistrict.Points?.length > 0 && <MapLayersControlPanel className="z-50" />}
|
||||
{GisStationsStaticDistrict && GisStationsStaticDistrict.Points?.length > 0 && (
|
||||
<MapLayersControlPanel className="z-50" />
|
||||
)}
|
||||
|
||||
<CoordinateInput onCoordinatesSubmit={handleCoordinatesSubmit} />
|
||||
<div id="map" ref={mapRef} className="z-0" style={{ height: "100vh", width: "100vw" }}></div>
|
||||
@@ -740,7 +838,11 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VersionInfoModal showVersionInfoModal={showVersionInfoModal} closeVersionInfoModal={closeVersionInfoModal} APP_VERSION={APP_VERSION} />
|
||||
<VersionInfoModal
|
||||
showVersionInfoModal={showVersionInfoModal}
|
||||
closeVersionInfoModal={closeVersionInfoModal}
|
||||
APP_VERSION={APP_VERSION}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user