diff --git a/application/ADBRimoFcp/ADBRimoFcpController.php b/application/ADBRimoFcp/ADBRimoFcpController.php
index f785a234d..5a1c36fb2 100644
--- a/application/ADBRimoFcp/ADBRimoFcpController.php
+++ b/application/ADBRimoFcp/ADBRimoFcpController.php
@@ -101,4 +101,28 @@ class ADBRimoFcpController extends TTCrud {
$counts['upd'], $counts['fcpNF'], $counts['noFCP'], $counts['noExtId']);
self::returnJson(['success' => true, 'message' => $msg]);
}
+
+ public function MapAction() {
+ Helper::renderVue($this, "ADBRimoFcpMap", "ADBRimoFcpMap", [
+ "MAPBOX_KEY" => TT_MAPBOX_TILE_API_TOKEN,
+ ]);
+ }
+
+
+ public function getAllFCPsAction() {
+ $input = json_decode(file_get_contents('php://input'), true);
+
+ $fcpList = ADBRimoFcp::getAll();
+ $fcpData = array_map(function ($fcp) {
+ return [
+ 'id' => $fcp->id,
+// 'rimo_ex_state' => $fcp->rimo_ex_state,
+// 'rimo_op_state' => $fcp->rimo_op_state,
+ 'gps_lat' => $fcp->gps_lat,
+ 'gps_long' => $fcp->gps_long
+ ];
+ }, $fcpList);
+
+ self::returnJson(['success' => true, 'data' => $fcpData]);
+ }
}
\ No newline at end of file
diff --git a/application/Preorder/PreorderController.php b/application/Preorder/PreorderController.php
index 309428100..bc4741032 100644
--- a/application/Preorder/PreorderController.php
+++ b/application/Preorder/PreorderController.php
@@ -1095,7 +1095,7 @@ class PreorderController extends mfBaseController {
return array_map(
fn($fcp) => ["id" => $fcp->name ?? null, "text" => $fcp->name ?? null, 'lat' => $fcp->gps_lat ?? null, 'lng' => $fcp->gps_long ?? null],
- ADBRimoFcp::getAll(["netzgebiet_id" => $campaign->network->adb_netzgebiet_id]) ?? []
+ ADBRimoFcp::getAll(["netzgebiet_id" => intval($campaign->network->adb_netzgebiet_id)]) ?? []
);
}
diff --git a/application/Preorder/PreorderModel.php b/application/Preorder/PreorderModel.php
index 30081f87c..94727c89c 100644
--- a/application/Preorder/PreorderModel.php
+++ b/application/Preorder/PreorderModel.php
@@ -1005,14 +1005,26 @@ class PreorderModel
}
}
}
-
- if (!empty($filter['fcp'])) {
+ if (!empty($filter['fcp']) && array_key_exists("preordercampaign_id", $filter)) {
$fcp = $filter['fcp'];
$db = FronkDB::singleton();
+ $campaign = new Preordercampaign($filter['preordercampaign_id']);
if (is_array($fcp)) {
- $items = array_map(fn($i) => "'" . $db->escape($i) . "'", array_filter($fcp));
- if ($items) $where .= " AND adb_hausnummer.rimo_fcp_name IN (" . implode(',', $items) . ")";
+ $items = array_map(fn($i) => ADBRimoFcp::getAll([
+ 'netzgebiet_id' => intval($campaign->network->adb_netzgebiet_id),
+ 'name' => $i])[0], array_filter($fcp));
+
+
+
+ $items = array_map(fn($i) => $i->id, array_filter($items));
+ if ($items) $where .= " AND adb_hausnummer.fcp_id IN (" . implode(',', $items) . ")";
} else {
+ $fcp = ADBRimoFcp::getAll([
+ 'netzgebiet_id' => intval($campaign->network->adb_netzgebiet_id),
+ 'name' => $fcp]);
+ if ($fcp) $fcp = $fcp[0]->id;
+ else $fcp = null;
+
$where .= " AND adb_hausnummer.rimo_fcp_name = '" . $db->escape($fcp) . "'";
}
}
diff --git a/public/bundler.php b/public/bundler.php
index 2f6228deb..ef6b66338 100644
--- a/public/bundler.php
+++ b/public/bundler.php
@@ -49,6 +49,7 @@ $jsFiles = [
"plugins/vue/tt-components/tt-textarea.js",
"plugins/vue/tt-components/tt-position-manager.js",
"plugins/vue/tt-components/tt-tooltip.js",
+ "plugins/vue/tt-components/tt-map.js",
];
diff --git a/public/js/pages/ADBRimoFcp/ADBRimoFcp.js b/public/js/pages/ADBRimoFcp/ADBRimoFcp.js
index 945feb06b..517613048 100644
--- a/public/js/pages/ADBRimoFcp/ADBRimoFcp.js
+++ b/public/js/pages/ADBRimoFcp/ADBRimoFcp.js
@@ -200,6 +200,7 @@ Vue.component('a-d-b-rimo-fcp', {
+
@@ -231,7 +232,8 @@ Vue.component('a-d-b-rimo-fcp', {
showImportLocationsModal: false,
networkAreas: window.TT_CONFIG?.CRUD_CONFIG?.columns?.find(col => col.key === 'netzgebiet_id')?.modal?.items || [],
fcpApiUrl: window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/ImportFCPs',
- locationsApiUrl: window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/ImportLocations'
+ locationsApiUrl: window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/ImportLocations',
+ window
}
},
methods: {
diff --git a/public/js/pages/ADBRimoFcpMap/ADBRimoFcpMap.js b/public/js/pages/ADBRimoFcpMap/ADBRimoFcpMap.js
new file mode 100644
index 000000000..5d2fd4747
--- /dev/null
+++ b/public/js/pages/ADBRimoFcpMap/ADBRimoFcpMap.js
@@ -0,0 +1,93 @@
+Vue.component('ADBRimoFcpMap', {
+ data() {
+ return {
+ mapMarkers: [],
+ isLoading: true,
+ error: null,
+ window,
+ fetchUrl: window.TT_CONFIG.BASE_PATH + '/ADBRimoFcp/getAllFCPs',
+ };
+ },
+ async created() {
+ await this.fetchAndPrepareData();
+ },
+ methods: {
+ async fetchAndPrepareData() {
+ this.isLoading = true;
+ this.error = null;
+ try {
+ const response = await axios.get(this.fetchUrl);
+ if (response.data && response.data.success && Array.isArray(response.data.data)) {
+ this.mapMarkers = response.data.data
+ .filter(fcp => fcp.gps_lat != null && fcp.gps_long != null)
+ .map(fcp => ({
+ lat: fcp.gps_lat,
+ lng: fcp.gps_long,
+ options: {
+ asyncPopupContent: async (markerData) => {
+ const response = await axios.get(`${this.window.TT_CONFIG.BASE_PATH}/ADBRimoFcp/getById?id=${fcp.id}`);
+ const fullFcpData = response.data;
+ return `
+
+
${fullFcpData.name}
+
+
+
+ | RIMO ID: |
+ ${fullFcpData.rimo_id} |
+
+
+ | RIMO Ex State: |
+ ${fullFcpData.rimo_ex_state} |
+
+
+ | RIMO Op State: |
+ ${fullFcpData.rimo_op_state} |
+
+
+ | Building Type: |
+ ${fullFcpData.building_type} |
+
+
+ | Coordinates: |
+
+
+ ${fullFcpData.gps_lat}, ${fullFcpData.gps_long}
+
+ |
+
+
+
+
+`; },
+ }
+ }));
+ } else {
+ console.error("Invalid data format from API:", response.data);
+ this.error = "Invalid data format received.";
+ this.mapMarkers = [];
+ }
+ } catch (err) {
+ console.error("Error fetching FCP data:", err);
+ this.error = "Failed to load FCP locations.";
+ this.mapMarkers = [];
+ } finally {
+ this.isLoading = false;
+ }
+ }
+ },
+ template: `
+
+
+ FCP Locations
+
+
+ {{ error }}
+
+
+
+ No FCP locations found.
+
+
+ `
+});
\ No newline at end of file
diff --git a/public/plugins/vue/tt-components/tt-map.js b/public/plugins/vue/tt-components/tt-map.js
new file mode 100644
index 000000000..43078f6c1
--- /dev/null
+++ b/public/plugins/vue/tt-components/tt-map.js
@@ -0,0 +1,185 @@
+Vue.component('tt-map', {
+ props: {
+ markersData: {
+ type: Array,
+ default: () => [] // Expecting [{ lat: Number, lng: Number, options: { maki?: Object, popup?: String, asyncPopupContent?: Function } }, ...]
+ },
+ config: {
+ type: Object,
+ default: () => ({}) // User overrides for defaults
+ },
+ loading: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ map: null,
+ markerLayer: null,
+ tileLayers: { streets: null, satellite: null },
+ mapType: localStorage.getItem('tt-map-type') || 'streets', // Default to 'streets' or stored preference
+ internalLoading: true,
+ scriptsLoaded: false,
+ };
+ },
+ computed: {
+ isLoading() {
+ return this.internalLoading || this.loading;
+ },
+ mapConfig() {
+ const defaults = {
+ center: [47.0707, 15.4395],
+ zoom: 13,
+ mapboxKey: window.TT_CONFIG?.MAPBOX_KEY,
+ streetsTileUrl: 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}',
+ streetsTileId: 'mapbox/streets-v11',
+ satelliteTileUrl: 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}',
+ satelliteTileId: 'mapbox/satellite-streets-v12', // Or 'mapbox/satellite-v9'
+ tileAttribution: '© Mapbox © OpenStreetMap',
+ clusterOptions: {},
+ makiMarkerOptions: { icon: "marker", color: "#3b82f6", size: "m" }
+ };
+ return { ...defaults, ...this.config }; // Merge user config over defaults
+ }
+ },
+ async mounted() {
+ try {
+ await this.loadScripts();
+ this.scriptsLoaded = true;
+ this.initializeMap();
+ this.updateMarkers();
+ this.internalLoading = false;
+ } catch (error) {
+ console.error("Map Initialization Error:", error);
+ this.internalLoading = false;
+ }
+ },
+ methods: {
+ loadScripts() {
+ const scripts = [
+ { type: 'link', url: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' },
+ { type: 'script', url: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js' },
+ { type: 'script', url: 'https://unpkg.com/leaflet-makimarkers@3.1.0/Leaflet.MakiMarkers.js' },
+ { type: 'link', url: 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css' },
+ { type: 'link', url: 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css' },
+ { type: 'script', url: 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js' }
+ ];
+
+ const promises = scripts.map(s => new Promise((resolve, reject) => {
+ let el;
+ if (s.type === 'script') {
+ el = document.createElement('script');
+ el.src = s.url; el.async = false; el.onload = resolve; el.onerror = reject;
+ } else {
+ el = document.createElement('link');
+ el.rel = 'stylesheet'; el.href = s.url; resolve();
+ }
+ if (el) document.head.appendChild(el); else reject();
+ }));
+ return Promise.all(promises);
+ },
+ initializeMap() {
+ if (!this.scriptsLoaded || !L || !L.MarkerClusterGroup || !L.MakiMarkers || !this.mapConfig.mapboxKey) return;
+
+ this.map = L.map(this.$refs.mapContainer, { preferCanvas: true }).setView(this.mapConfig.center, this.mapConfig.zoom);
+ L.MakiMarkers.accessToken = this.mapConfig.mapboxKey;
+
+ this.tileLayers.streets = L.tileLayer(this.mapConfig.streetsTileUrl, {
+ attribution: this.mapConfig.tileAttribution, maxZoom: 18, id: this.mapConfig.streetsTileId,
+ tileSize: 512, zoomOffset: -1, accessToken: this.mapConfig.mapboxKey
+ });
+ this.tileLayers.satellite = L.tileLayer(this.mapConfig.satelliteTileUrl, {
+ attribution: this.mapConfig.tileAttribution, maxZoom: 18, id: this.mapConfig.satelliteTileId,
+ tileSize: 512, zoomOffset: -1, accessToken: this.mapConfig.mapboxKey
+ });
+
+ this.tileLayers[this.mapType].addTo(this.map); // Add initial layer based on preference
+
+ this.markerLayer = L.markerClusterGroup(this.mapConfig.clusterOptions);
+ this.map.addLayer(this.markerLayer);
+
+ // Invalidate size after initial load if container might not have been ready
+ this.$nextTick(() => {
+ this.map.invalidateSize();
+ });
+ // Add resize listener
+ window.addEventListener('resize', this.handleResize);
+ },
+ updateMarkers() {
+ if (!this.map || !this.markerLayer || !this.scriptsLoaded) return;
+ this.markerLayer.clearLayers();
+ const markersToAdd = [];
+ this.markersData.forEach(data => {
+ if (data.lat != null && data.lng != null) {
+ const makiOptions = { ...this.mapConfig.makiMarkerOptions, ...(data.options?.maki || {}) };
+ const icon = L.MakiMarkers.icon(makiOptions);
+ const marker = L.marker([data.lat, data.lng], { icon: icon });
+
+ if (data.options?.popup) {
+ marker.bindPopup(data.options.popup);
+ } else if (data.options?.asyncPopupContent && typeof data.options.asyncPopupContent === 'function') {
+ marker.bindPopup(() => ''); // Initial content
+ marker.on('popupopen', async (e) => {
+ const popup = e.popup;
+ try {
+ const content = await data.options.asyncPopupContent(data); // Pass marker data to function
+ popup.setContent(content);
+ } catch (error) {
+ console.error("Error loading popup content:", error);
+ popup.setContent('Failed to load content.
');
+ }
+ popup.update(); // Adjust size
+ });
+ }
+
+ if (data.options?.tooltip) marker.bindTooltip(data.options.tooltip);
+ markersToAdd.push(marker);
+ }
+ });
+ if (markersToAdd.length > 0) this.markerLayer.addLayers(markersToAdd);
+ },
+ toggleMapType() {
+ this.map.removeLayer(this.tileLayers[this.mapType]);
+ this.mapType = this.mapType === 'streets' ? 'satellite' : 'streets';
+ this.tileLayers[this.mapType].addTo(this.map);
+ localStorage.setItem('tt-map-type', this.mapType);
+ },
+ handleResize() {
+ if (this.map) {
+ // Use debounce if resize events fire too rapidly
+ this.map.invalidateSize();
+ }
+ }
+ },
+ watch: {
+ markersData: { handler() { this.updateMarkers(); }, deep: true },
+ loading(newVal) {
+ // Optional: Invalidate map size when loading finishes, in case container size changed
+ if (!newVal && this.map) {
+ this.$nextTick(() => this.map.invalidateSize());
+ }
+ }
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', this.handleResize);
+ if (this.map) {
+ this.map.remove();
+ this.map = null;
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+ `
+});
\ No newline at end of file