Files
sgeUpdated/sge-frontend/src/views/Map.js

730 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect } from "react";
import {
MapContainer,
Marker,
Polygon,
Popup,
TileLayer,
Tooltip,
useMap,
LayerGroup,
} from "react-leaflet";
import "../components/leaflet.css";
import { useState, useRef } from "react";
import {
Button,
Label,
Modal,
ModalHeader,
ModalBody,
Row,
Col,
Input,
Card,
CardHeader,
CardTitle,
} from "reactstrap";
import Select from "react-select";
import { useTranslation } from "react-i18next";
import DataInputGroup from "../components/data-input/index.js";
import { useSelector, useDispatch } from "react-redux";
import { getCities } from "../redux/actions/cities";
import { v4 as uuidv4 } from "uuid";
import { getCity } from "../redux/actions/city";
import { getDistrict } from "../redux/actions/district";
import {
getOrganisations,
getOrganisationById,
} from "../redux/actions/organisations";
import { getAreasWithCriteria } from "../redux/actions/areas";
import { ChromePicker } from "react-color";
import { customFilterForSelect } from "../utility/Utils";
import { permissionCheck } from "../components/permission-check";
import { getDataCenters } from "../redux/actions/dataCenter";
const ColorPicker = ({ selectedColors, setSelectedColors, index }) => {
const [showColorPicker, setShowColorPicker] = useState(false);
useEffect(() => {
const storedColors = localStorage.getItem("selectedMapColors");
if (storedColors) {
setSelectedColors(JSON.parse(storedColors));
}
}, []);
useEffect(() => {
localStorage.setItem("selectedMapColors", JSON.stringify(selectedColors));
}, [selectedColors]);
const handleColorChange = (color) => {
const rgbaColor = `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, 0.3)`;
const updatedColors = [...selectedColors];
updatedColors[index] = rgbaColor;
setSelectedColors(updatedColors);
};
const handlePickerOutsideClick = (event) => {
// Renk seçici dışına tıklama kontrolü
if (
event.target.closest(".color-picker-container") === null &&
event.target.closest(".color-box") === null
) {
setShowColorPicker(false);
}
};
useEffect(() => {
// Tüm belgeye tıklama olayını dinle
document.addEventListener("click", handlePickerOutsideClick);
return () => {
// Bileşen kaldırıldığında tıklama olayının dinlemesini kaldır
document.removeEventListener("click", handlePickerOutsideClick);
};
}, []);
return (
<div className="color-picker-container">
<div
key={index}
className="color-box"
style={{
backgroundColor: selectedColors[index],
width: "30px",
height: "15px",
display: "inline-block",
marginRight: "10px",
cursor: "pointer",
}}
onClick={() => setShowColorPicker(true)}
></div>
{showColorPicker && (
<div style={{ position: "absolute", zIndex: 1000 }}>
<ChromePicker
color={selectedColors[index]}
onChange={handleColorChange}
/>
</div>
)}
</div>
);
};
const Map = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const citiesStore = useSelector((state) => state.cities);
const cityStore = useSelector((state) => state.city);
const districtStore = useSelector((state) => state.district);
const OrganisationsStore = useSelector((state) => state.organizations);
const areasStore = useSelector((state) => state.areas);
const dataCenterStore = useSelector((state) => state.dataCenter);
const [cities, setCities] = useState([]);
const [districts, setDistricts] = useState([]);
const [neighborhoods, setNeighborhoods] = useState([]);
const [organizationOptions, setOrganizationOptions] = useState([]);
const organizationId = localStorage.getItem("organizationId");
const roleTag = localStorage.getItem("roleTag");
const [selectedOrganization, setSelectedOrganization] = useState({
label: localStorage.getItem("organizationName"),
value: organizationId,
});
const [areasOptions, setAreasOptions] = useState([]);
const [done, setDone] = useState(false);
const [selectedCity, setSelectedCity] = useState(null);
const [selectedDistrict, setSelectedDistrict] = useState(null);
const [districtView, setDistrictView] = useState(false);
const [neighbourhoodView, setNeighbourhoodView] = useState(false);
const cityRefs = useRef({});
const districtRefs = useRef({});
const neighbourhoodRefs = useRef({});
const [zoom, setZoom] = useState(6.7);
const [center, setCenter] = useState({ lat: 38.5, lng: 35.27 });
const [showDataInputModal, setShowDataInputModal] = useState(false);
const [inputData, setInputData] = useState({
organization: selectedOrganization,
});
const [referance, setReferance] = useState(
Number(localStorage.getItem("referance")) || 1000
);
const [referance2, setReferance2] = useState(
Number(localStorage.getItem("referance2")) || 2000
);
const initialColors = [
"rgba(44,232,44,0.3)",
"rgba(234,234,17,0.3)",
"rgba(228,19,19,0.3)",
];
const [selectedColors, setSelectedColors] = useState(initialColors);
const currentYear = new Date().getFullYear();
const [year, setYear] = useState({ value: currentYear, label: currentYear });
const renderYearOptions = () => {
const years = [];
for (let year = currentYear; year >= 1990; year--) {
years.push({ label: year, value: year });
}
return years;
};
useEffect(() => {
dispatch(getCities());
dispatch(getDataCenters());
}, []);
useEffect(() => {
if (selectedOrganization?.value != "undefined") {
dispatch(getAreasWithCriteria(selectedOrganization.value));
}
}, [selectedOrganization]);
useEffect(() => {
setAreasOptions([]);
const citiesOptions =
areasStore?.areasWithCriteria
?.map((area) =>
Array.isArray(area?.cities) // cities'in bir dizi olup olmadığını kontrol edin
? area.cities.map((city) => ({
value: city?.id,
label: city?.name,
type: "city",
}))
: []
)
.flat() || []; // Eğer map boş dönerse, `flat` sonrası boş bir dizi kullan
const districtsOptions = areasStore?.areasWithCriteria
?.map((area) => {
return area?.districts?.map((district) => {
return {
value: district?.id,
label: district?.name + " (" + district?.city?.name + ")",
type: "district",
};
});
})
.flat();
const neighborhoodsOptions = areasStore?.areasWithCriteria
?.map((area) => {
return area?.neighborhoods?.map((neighborhood) => {
return {
value: neighborhood?.id,
label:
neighborhood?.name +
" (" +
neighborhood?.district?.name +
" / " +
neighborhood?.district?.city?.name +
")",
type: "neighborhood",
};
});
})
.flat();
setAreasOptions([
...(citiesOptions || []),
...(districtsOptions || []),
...(neighborhoodsOptions || []),
]);
}, [areasStore]);
useEffect(() => {
if (roleTag === "SUPER_ADMIN") {
dispatch(getOrganisations());
} else {
dispatch(getOrganisationById(organizationId));
}
}, []);
const handleDataInputButtonPressed = ({ area, type }) => {
const areaName =
type === "neighborhood"
? `${area?.name} / ${area?.district?.name} / ${area?.district?.city?.name}`
: type === "district"
? `${area?.name} / ${area?.city?.name}`
: area?.name;
const areaType =
type === "district"
? "district"
: type === "city"
? "city"
: type === "neighborhood"
? "neighborhood"
: "";
setShowDataInputModal(true);
setInputData({
...inputData,
area: { value: area?.id, label: areaName, type: areaType },
});
};
useEffect(() => {
if (!citiesStore?.cities || !year?.value) {
console.error("Cities or year is not defined.");
return;
}
setCities(() => {
const updatedCities = citiesStore.cities.map((city) => {
const mainDataTables = city.mainDataTables
? city.mainDataTables.filter(
(data) => data.year === year.value.toString()
)
: [];
const total = mainDataTables.reduce(
(acc, data) => acc + (data?.totalEmission || 0),
0
);
return { ...city, mainDataTables, total };
});
return updatedCities;
});
}, [citiesStore?.cities, year?.value]);
useEffect(() => {
if (selectedCity != undefined) {
dispatch(getCity(selectedCity?.id));
}
}, [selectedCity]);
useEffect(() => {
if (selectedDistrict != undefined && selectedDistrict.length != 0) {
dispatch(getDistrict(selectedDistrict?.id));
}
}, [selectedDistrict]);
useEffect(() => {
if (!cityStore?.city?.districts || !year.value) {
return;
}
setDistricts(() => {
const updatedDistricts = cityStore.city.districts?.map((district) => {
const mainDataTables = district.mainDataTables.filter(
(data) => data.year === year.value.toString()
);
const total = mainDataTables.reduce(
(acc, data) => acc + data.totalEmission,
0
);
return { ...district, mainDataTables, total };
});
return updatedDistricts;
});
}, [selectedCity, cityStore?.city, year.value]);
useEffect(() => {
if (!districtStore?.district?.neighborhoods || !year.value) {
return;
}
setNeighborhoods(() => {
const updatedNeighborhoods = districtStore.district?.neighborhoods?.map(
(neighborhood) => {
const mainDataTables = neighborhood.mainDataTables.filter(
(data) => data.year === year.value.toString()
);
const total = mainDataTables.reduce(
(acc, data) => acc + data.totalEmission,
0
);
return { ...neighborhood, mainDataTables, total };
}
);
return updatedNeighborhoods;
});
}, [selectedDistrict, districtStore?.district, year.value]);
useEffect(() => {
let organizationOptions = [];
if (
OrganisationsStore.organization &&
OrganisationsStore.organization.length !== 0
) {
organizationOptions.push({
value: OrganisationsStore.organization.id,
label: OrganisationsStore.organization.tag,
});
if (OrganisationsStore.organization.children) {
organizationOptions = [
...organizationOptions,
...OrganisationsStore.organization.children.map((organization) => ({
value: organization.child.id,
label: organization.child.tag,
})),
];
}
} else {
organizationOptions = OrganisationsStore.dataOrganization.map(
(organization) => ({
value: organization.id,
label: organization.tag,
})
);
}
setOrganizationOptions(organizationOptions);
}, [OrganisationsStore]);
const renderDataInputModal = () => {
return (
<Modal
isOpen={showDataInputModal}
toggle={() => setShowDataInputModal(!showDataInputModal)}
className="modal-dialog-centered"
size="lg"
>
<ModalHeader toggle={() => setShowDataInputModal(!showDataInputModal)}>
{t("DataInput.dataInput")} - {inputData?.area?.label}
</ModalHeader>
<ModalBody>
<DataInputGroup inputData={inputData} setInputData={setInputData} />
</ModalBody>
</Modal>
);
};
const grayOptions = {
color: "gray",
fillColor: "transparent",
opacity: 1,
};
const greenOptions = {
color: "gray",
fillColor: selectedColors[0],
opacity: 1,
};
const yellowOptions = {
color: "gray",
fillColor: selectedColors[1],
opacity: 1,
};
const redOptions = {
color: "gray",
fillColor: selectedColors[2],
opacity: 1,
};
const pathOptions = (total) => {
return total > referance2
? redOptions
: referance < total && total < referance2
? yellowOptions
: total != 0 && total < referance
? greenOptions
: grayOptions;
};
const turkeyBounds = [
[34.42, 44.83],
[43.45, 25.62],
];
const RenderDistrictView = () => {
return districts?.map((district, index) => {
return (
<Polygon
key={uuidv4()}
ref={(c) => {
districtRefs.current[index] = c;
if (index === district.length - 1 && !done) {
setDone(true);
}
}}
fillOpacity={1}
pathOptions={pathOptions(district.total)}
positions={convertCoordinates(district.coordinates)}
eventHandlers={{
mouseover: () => {
const district = districtRefs.current[index];
district.openPopup();
},
}}
>
<Tooltip permanent direction="center" className="tooltip">
{district.name}
</Tooltip>
{
<Popup closeButton={true} autoPan={false}>
<h5 className="w-100 text-center">{district.name}</h5>
<Button
className="w-100 mb-1"
color="primary"
onClick={() => {
setSelectedDistrict(district);
setNeighbourhoodView(true);
setDistrictView(false);
setSelectedCity(null);
setZoom(11.0);
let convertCordinates = convertCoordinates(
district.coordinates
);
let length = convertCordinates[0][0][0].length;
let mlength = ((length + 1) / 2).toFixed(0);
let lat1 = convertCordinates[0][0][0][0];
let lng1 = convertCordinates[0][0][0][1];
let lat2 = convertCordinates[0][0][mlength][0];
let lng2 = convertCordinates[0][0][mlength][1];
setCenter({
lat: ((lat1 + lat2) / 2).toFixed(2),
lng: ((lng1 + lng2) / 2).toFixed(2),
});
}}
>
{t("Areas.neighborhoods")}
</Button>
<br></br>
{areaCheck({ areaId: district.id }) &&
permissionCheck("dataset_create") && (
<Button
className="w-100 mb-1"
color="primary"
onClick={() =>
handleDataInputButtonPressed({
area: district,
type: "district",
})
}
>
{t("DataInput.dataInput")}
</Button>
)}
<br></br>
<Button
className="w-100"
color="primary"
onClick={() => {
setDistrictView(false);
setCenter({ lat: 38, lng: 37 });
setZoom(6.7);
}}
>
{t("Map.goBack")}
</Button>
</Popup>
}
</Polygon>
);
});
};
const neighborhoodTooltipBackground = (total) => {
return total > referance2
? selectedColors[2]
: referance < total && total < referance2
? selectedColors[1]
: total != 0 && total < referance
? selectedColors[0]
: "white";
};
const renderNeighbourhoodView = () => {
return neighborhoods?.map((neighbourhood, index) => {
return (
<Marker
opacity={0}
zoom={10.75}
key={uuidv4()}
ref={(c) => {
// <--- add city refs to ref object here
neighbourhoodRefs.current[index] = c;
if (index === neighbourhood.length - 1 && !done) {
setDone(true);
}
}}
fillOpacity={1}
pathOptions={pathOptions(neighbourhood.total)}
position={[
Number(neighbourhood?.minLat + neighbourhood?.maxLat) / 2,
Number(neighbourhood?.minLong + neighbourhood?.maxLong) / 2,
]}
eventHandlers={{
mouseover: () => {
const neighbourhood = neighbourhoodRefs.current[index];
neighbourhood.openPopup();
},
}}
>
<Tooltip permanent direction="center" className="tooltip">
{neighbourhood.name}
</Tooltip>
<Popup closeButton={true} autoPan={false}>
{areaCheck({ areaId: neighbourhood.id }) &&
permissionCheck("dataset_create") && (
<Button
className="w-100 mb-1"
color="primary"
onClick={() =>
handleDataInputButtonPressed({
area: neighbourhood,
type: "neighborhood",
})
}
>
{t("DataInput.dataInput")}
</Button>
)}
<br></br>
<Button
className="w-100"
color="primary"
onClick={() => {
setNeighbourhoodView(false);
setCenter({ lat: 38, lng: 37 });
setZoom(6.7);
}}
>
{t("Map.goBack")}
</Button>
</Popup>
</Marker>
);
});
};
function ChangeZoom({ center, zoom }) {
const map = useMap();
map.setView(center, zoom);
return null;
}
function convertCoordinates(coords) {
const editedCoordinates = JSON.parse(coords);
return editedCoordinates;
}
function areaCheck({ areaId }) {
return areasOptions?.some((area) => areaId === area.value);
}
const renderDataCenterMarkers = () => {
if (!dataCenterStore?.dataCenters) return null;
return (
<LayerGroup>
{dataCenterStore.dataCenters.map((dc) => {
if (!dc.latitude || !dc.longitude) return null;
return (
<Marker
key={dc.id}
position={[dc.latitude, dc.longitude]}
>
<Popup>
<div className="data-center-popup">
<h5 className="mb-2">{dc.dataCenter}</h5>
<p className="mb-1"><strong>{t('DataCenter.number')}:</strong> {dc.number}</p>
<p className="mb-1"><strong>{t('DataCenter.externalId')}:</strong> {dc.externalId}</p>
<p className="mb-1"><strong>{t('DataCenter.city')}:</strong> {dc.area?.cities?.map(city => city.name).join(', ') || "-"}</p>
{dc.area && <p className="mb-1"><strong>{t('Area')}:</strong> {dc.area.tag}</p>}
{dc.projects?.length > 0 && (
<p className="mb-1">
<strong>{t('Projects')}:</strong> {dc.projects.length}
</p>
)}
{dc.ayposURL && (
<Button
className="w-100 mb-1"
color="primary"
onClick={() => window.open(dc.ayposURL, '_blank')}
>
Dashboard
</Button>
)}
</div>
</Popup>
<Tooltip>{dc.dataCenter}</Tooltip>
</Marker>
);
})}
</LayerGroup>
);
};
return (
<Card>
<CardHeader className="border-bottom">
<CardTitle tag="h4">{t("Map.title")}</CardTitle>
</CardHeader>
<div style={{ height: "700px", width: "100%" }}>
<MapContainer
center={center}
zoom={zoom}
style={{ height: "100%", width: "100%" }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/>
{renderDataCenterMarkers()}
{neighbourhoodView
? renderNeighbourhoodView(selectedDistrict)
: districtView
? RenderDistrictView(selectedCity)
: cities.map((city, index) => {
return (
<Polygon
key={uuidv4()}
ref={(c) => {
cityRefs.current[index] = c;
if (index === cities.length - 1 && !done) {
setDone(true);
}
}}
fillOpacity={1}
pathOptions={pathOptions(city.total)}
positions={convertCoordinates(city.coordinates)}
eventHandlers={{
click: () => {
setSelectedCity(city);
setDistrictView(true);
setZoom(8.0);
let convertCordinates = convertCoordinates(city.coordinates);
let length = convertCordinates[0][0][0].length;
let mlength = ((length + 1) / 2).toFixed(0);
let lat1 = convertCordinates[0][0][0][0];
let lng1 = convertCordinates[0][0][0][1];
let lat2 = convertCordinates[0][0][mlength][0];
let lng2 = convertCordinates[0][0][mlength][1];
setCenter({
lat: ((lat1 + lat2) / 2).toFixed(2),
lng: ((lng1 + lng2) / 2).toFixed(2),
});
}
}}
>
<Tooltip permanent direction="center">
{city.name}
</Tooltip>
</Polygon>
);
})}
{renderDataInputModal()}
</MapContainer>
</div>
</Card>
);
};
export default Map;