diff --git a/Layout/default/Device/Detail.php b/Layout/default/Device/Detail.php index 9c43f60b4..0925dfd2a 100644 --- a/Layout/default/Device/Detail.php +++ b/Layout/default/Device/Detail.php @@ -367,7 +367,7 @@ foreach ($devicesall as $deviceall) {

Config Backups

- devicetype->devicemanufactor->config_backup > count()): ?> + devicetype->devicemanufactor->config_backup): ?> vlan_ipv6))) $('[data-toggle="popover"]').popover(); }); - + - - - + + + diff --git a/application/Pop/PopController.php b/application/Pop/PopController.php index 5bb6f1e4a..e03af32a7 100644 --- a/application/Pop/PopController.php +++ b/application/Pop/PopController.php @@ -17,6 +17,25 @@ class PopController extends mfBaseController } } + private function getMapCategories() + { + $categories = []; + foreach (PopModel::$categoryArray as $id => $cat) { + $categories[] = [ + 'id' => $id, + 'name' => $cat['name'], + 'icon' => 'assets/img/markers/pop_' . $id . '.png', + ]; + } + + $categories[] = [ + 'id' => null, + 'name' => 'Unbekannt', + 'icon' => 'assets/img/markers/pop_unknown.png', + ]; + return $categories; + } + protected function indexAction() { $networks = array_map(function ($network) { @@ -30,7 +49,7 @@ class PopController extends mfBaseController return [ "id" => $pop->id, "name" => $pop->name, - "category" => $pop->category, + "category" => $pop->category ?: 99, "networkArea" => $pop->networks, "location" => $pop->location, "state" => $pop->state, @@ -45,6 +64,8 @@ class PopController extends mfBaseController ]; }, PopModel::getAlladv()); + $categories = $this->getMapCategories(); + $JSGlobals = ["BASE_URL" => self::getUrl(""), "DASHBOARD_URL" => self::getUrl("Dashboard"), "MFAPPNAME" => MFAPPNAME_SLUG, @@ -55,11 +76,20 @@ class PopController extends mfBaseController ], "NETWORKS" => $networks, "POPS" => $pops, + "CATEGORIES" => $categories, "IS_ADMIN" => $this->me->is("Admin"), + "MAPBOX_TOKEN" => TT_MAPBOX_TILE_API_TOKEN, ]; $this->layout()->set("vueViewName", "Pop"); $this->layout()->set("JSGlobals", $JSGlobals); + $this->layout()->set("additionalCSS", [ + "assets/css/leaflet.css", + ]); + $this->layout()->set("additionalJS", [ + "assets/js/leaflet.js", + "assets/js/leaflet.MakiMarkers.js" + ]); $this->layout()->setTemplate("VueViews/Vue"); } @@ -112,6 +142,7 @@ class PopController extends mfBaseController { $network_id = 90; $this->layout()->set("network_id", $network_id); + $this->layout()->set("categories", $this->getMapCategories()); $this->layout()->setTemplate("Pop/Map"); } @@ -258,28 +289,23 @@ class PopController extends mfBaseController $home_id = $this->request->home_id; if (!$fiber_id && !$home_id) { - return mfBaseController::returnJson(mfResponse::BadRequest(['message' => 'Ungültige Faser-ID oder Home-ID'])); + return mfBaseController::returnJson(mfResponse::BadRequest(['message' => 'Ungültige ID'])); } - if ($home_id) { + if ($home_id && !$fiber_id) { $sql = "SELECT id FROM FiberPlanFiber WHERE home_id = '" . $db->escape($home_id) . "' LIMIT 1"; $res = $db->query($sql); if ($db->num_rows($res)) { $row = $db->fetch_array($res); $fiber_id = $row['id']; - } else { - return mfBaseController::returnJson(mfResponse::NotFound(['message' => 'Keine Faser für Home-ID gefunden'])); } } $fiber = new FiberPlanFiber($fiber_id); - if (!$fiber->id) { return mfBaseController::returnJson(mfResponse::NotFound(['message' => 'Faser nicht gefunden'])); } - $this->log->debug("Lade Faser-Strecke für Faser ID: $fiber_id"); - $details = $fiber->toArray(); $details['customer_cable_type'] = $fiber->customer_cable_type; $details['customer_cable_fiber_nr'] = $fiber->customer_cable_fiber_nr; @@ -293,26 +319,217 @@ class PopController extends mfBaseController if ($fiber->address || $fiber->home_id) { $customerGps = $this->geocodeAddress($fiber->address, $fiber->home_id); - if ($customerGps) { - $details['customer_gps'] = $customerGps; - $this->log->debug("GPS für Kunde gefunden: " . json_encode($customerGps)); - } + if ($customerGps) $details['customer_gps'] = $customerGps; } $debug = []; - $debug['start_fiber'] = [ - 'id' => $fiber->id, - 'fiber_nr_cable' => $fiber->fiber_nr_cable, - 'branch_type' => $fiber->branch_type, - 'branch_cable_nr' => $fiber->branch_cable_nr, - 'branch_fiber_nr' => $fiber->branch_fiber_nr - ]; if ($home_id) { - $this->log->debug("=== MODUS: Rückwärts-Trace (von home_id) ==="); + $cableChain = $this->buildCompleteCableChain($fiber); + if (count($cableChain) > 0) { + $mainCable = $cableChain[0]['cable']; + $mainFiber = $cableChain[0]['fiber']; + $cable_route_data = FiberPlanCableModel::getCableRoute($mainCable->id); + $cable_route_array = []; + foreach ($cable_route_data as $station) $cable_route_array[] = $station['name']; + $allFibers = FiberPlanFiberModel::getByCableAndSheet($mainCable->id, null); + $fibersArray = []; + foreach ($allFibers as $f) { + $fibersArray[] = [ + 'id' => $f->id, 'fiber_nr_cable' => $f->fiber_nr_cable, + 'fiber_color' => $f->fiber_color, 'fiber_color_hex' => $f->fiber_color_hex, + 'bundle_nr' => $f->bundle_nr, 'bundle_color' => $f->bundle_color, 'bundle_color_hex' => $f->bundle_color_hex, + 'branch_type' => $f->branch_type, 'branch_cable_nr' => $f->branch_cable_nr, + 'branch_from_location' => $f->branch_from_location, 'branch_fiber_nr' => $f->branch_fiber_nr + ]; + } + + $branchPoints = []; + $sql = "SELECT id, description as name, gps_lat, gps_long, object_type, 'dispatcher' as type + FROM FiberPlanDispatcher WHERE network_id = 90 AND object_type = 4 AND gps_lat IS NOT NULL"; + $res = $db->query($sql); + while ($data = $db->fetch_array($res)) { + $branchPoints[] = ['id' => $data['id'], 'name' => $data['name'], 'gps_lat' => $data['gps_lat'], 'gps_long' => $data['gps_long'], 'object_type' => intval($data['object_type']), 'type' => $data['type']]; + } + + $details['cable_info'] = [ + 'id' => $mainCable->id, 'description' => $mainCable->description, 'fibers' => $fibersArray, + 'diameter' => $mainCable->diameter, 'cable_route_array' => $cable_route_array, + 'cable_route_full' => $cable_route_data, 'coordinates' => $mainCable->coordinates, + 'location' => $mainFiber->location, 'branch_points' => $branchPoints + ]; + + $allCablesForMatching = []; + foreach ($cableChain as $chainItem) { + $c = $chainItem['cable']; + $coords = json_decode($c->coordinates, true); + if ($coords) $allCablesForMatching[] = ['id' => $c->id, 'description' => $c->description, 'coordinates' => $coords]; + } + $sql = "SELECT id, description, coordinates FROM FiberPlanCable WHERE network_id = 90 AND coordinates IS NOT NULL AND coordinates != '' AND coordinates != '[]'"; + $res = $db->query($sql); + while ($c = $db->fetch_array($res)) { + $coords = json_decode($c['coordinates'], true); + if ($coords) { + $exists = false; foreach($allCablesForMatching as $ex) { if($ex['id'] == $c['id']) $exists=true; } + if(!$exists) $allCablesForMatching[] = ['id' => $c['id'], 'description' => $c['description'], 'coordinates' => $coords]; + } + } + $details['all_cables'] = $allCablesForMatching; + } + + if (count($cableChain) > 1) { + $details['branch_path'] = $this->buildBranchPathFromChain($cableChain, 1, $debug); + } + + } else { + $this->log->debug("=== MODUS: Vorwärts-Trace ==="); + + if ($fiber->cable_id) { + $cable = new FiberPlanCable($fiber->cable_id); + if ($cable->id) { + $cable_route_data = FiberPlanCableModel::getCableRoute($cable->id); + $cable_route_array = []; + foreach ($cable_route_data as $station) $cable_route_array[] = $station['name']; + + $branchPoints = []; + $sql = "SELECT id, description as name, gps_lat, gps_long, object_type, 'dispatcher' as type + FROM FiberPlanDispatcher WHERE network_id = 90 AND object_type = 4 AND gps_lat IS NOT NULL"; + $res = $db->query($sql); + while ($data = $db->fetch_array($res)) { + $branchPoints[] = ['id' => $data['id'], 'name' => $data['name'], 'gps_lat' => $data['gps_lat'], 'gps_long' => $data['gps_long'], 'object_type' => intval($data['object_type']), 'type' => $data['type']]; + } + + $allFibers = FiberPlanFiberModel::getByCableAndSheet($cable->id, null); + $fibersArray = []; + foreach ($allFibers as $f) { + $fibersArray[] = [ + 'id' => $f->id, 'fiber_nr_cable' => $f->fiber_nr_cable, 'fiber_color' => $f->fiber_color, 'fiber_color_hex' => $f->fiber_color_hex, + 'bundle_nr' => $f->bundle_nr, 'bundle_color' => $f->bundle_color, 'bundle_color_hex' => $f->bundle_color_hex, + 'branch_type' => $f->branch_type, 'branch_cable_nr' => $f->branch_cable_nr, + 'branch_from_location' => $f->branch_from_location, 'branch_fiber_nr' => $f->branch_fiber_nr + ]; + } + + $details['cable_info'] = [ + 'id' => $cable->id, 'description' => $cable->description, 'fibers' => $fibersArray, + 'diameter' => $cable->diameter, 'cable_route_array' => $cable_route_array, + 'cable_route_full' => $cable_route_data, 'coordinates' => $cable->coordinates, + 'location' => $fiber->location, 'branch_points' => $branchPoints + ]; + } + } + + if ($fiber->branch_type === 'Abzweigkabel' && $fiber->branch_cable_nr) { + $details['branch_path'] = $this->traceBranchPath($fiber, 0, 10, $debug); + } + + $allCablesForMatching = []; + $sql = "SELECT id, description, coordinates FROM FiberPlanCable WHERE network_id = 90 AND coordinates IS NOT NULL AND coordinates != '' AND coordinates != '[]'"; + $res = $db->query($sql); + while ($c = $db->fetch_array($res)) { + $coords = json_decode($c['coordinates'], true); + if ($coords) $allCablesForMatching[] = ['id' => $c['id'], 'description' => $c['description'], 'coordinates' => $coords]; + } + $details['all_cables'] = $allCablesForMatching; + } + + $details['debug'] = $debug; + return mfBaseController::returnJson(mfResponse::Ok(['fiber' => $details])); + } + + protected function getAllFiberPathsForHomeAction() + { + $db = FronkDB::singleton(); + $home_id = $this->request->home_id; + + if (!$home_id) { + return mfBaseController::returnJson(mfResponse::BadRequest(['message' => 'Ungültige Home-ID'])); + } + + $sql = "SELECT id FROM FiberPlanFiber WHERE home_id = '" . $db->escape($home_id) . "'"; + $res = $db->query($sql); + + $fiberIds = []; + if ($db->num_rows($res)) { + while ($row = $db->fetch_array($res)) { + $fiberIds[] = $row['id']; + } + } + + if (empty($fiberIds)) { + return mfBaseController::returnJson(mfResponse::NotFound(['message' => 'Keine Fasern für Home-ID gefunden'])); + } + + $globalBranchPoints = []; + $sqlBP = "SELECT id, description as name, gps_lat, gps_long, object_type, 'dispatcher' as type + FROM FiberPlanDispatcher + WHERE network_id = 90 AND object_type IN (1,2,3,4) + AND gps_lat IS NOT NULL AND gps_long IS NOT NULL"; + $resBP = $db->query($sqlBP); + if ($db->num_rows($resBP)) { + while ($data = $db->fetch_array($resBP)) { + $globalBranchPoints[] = [ + 'id' => $data['id'], + 'name' => $data['name'], + 'gps_lat' => $data['gps_lat'], + 'gps_long' => $data['gps_long'], + 'object_type' => intval($data['object_type']), + 'type' => $data['type'] + ]; + } + } + + $globalCablesWithCoords = []; + $sqlCables = "SELECT id, description, coordinates + FROM FiberPlanCable + WHERE network_id = 90 + AND coordinates IS NOT NULL + AND coordinates != '' + AND coordinates != '[]'"; + $resCables = $db->query($sqlCables); + while ($cableData = $db->fetch_array($resCables)) { + $coords = json_decode($cableData['coordinates'], true); + if ($coords && is_array($coords) && count($coords) > 0) { + $globalCablesWithCoords[] = [ + 'id' => $cableData['id'], + 'description' => $cableData['description'], + 'coordinates' => $coords + ]; + } + } + + $allPaths = []; + + foreach ($fiberIds as $fiber_id) { + $fiber = new FiberPlanFiber($fiber_id); + if (!$fiber->id) continue; + $details = $fiber->toArray(); + $details['customer_cable_type'] = $fiber->customer_cable_type; + $details['customer_cable_fiber_nr'] = $fiber->customer_cable_fiber_nr; + $details['customer_connector_type'] = $fiber->customer_connector_type; + $details['customer_cable_spec'] = $fiber->customer_cable_spec; + $details['customer_fiber_range'] = $fiber->customer_fiber_range; + $details['bundle_nr'] = $fiber->bundle_nr; + $details['bundle_color'] = $fiber->bundle_color; + $details['bundle_color_hex'] = $fiber->bundle_color_hex; + $details['fiber_nr_bundle'] = $fiber->fiber_nr_bundle; + + if ($fiber->address || $fiber->home_id) { + $customerGps = $this->geocodeAddress($fiber->address, $fiber->home_id); + if ($customerGps) { + $details['customer_gps'] = $customerGps; + } + } + + $debug = []; + $debug['start_fiber'] = [ + 'id' => $fiber->id, + 'fiber_nr_cable' => $fiber->fiber_nr_cable, + 'branch_type' => $fiber->branch_type + ]; $cableChain = $this->buildCompleteCableChain($fiber); - $debug['cable_chain_count'] = count($cableChain); + $debug['cable_chain'] = array_map(function($item) { return [ 'cable_id' => $item['cable']->id, @@ -333,12 +550,17 @@ class PopController extends mfBaseController $cable_route_array[] = $station['name']; } - $allFibers = FiberPlanFiberModel::getByCableAndSheet($mainCable->id, null); + $allMainFibers = FiberPlanFiberModel::getByCableAndSheet($mainCable->id, null); $fibersArray = []; - foreach ($allFibers as $f) { + foreach ($allMainFibers as $f) { $fibersArray[] = [ 'id' => $f->id, 'fiber_nr_cable' => $f->fiber_nr_cable, + 'fiber_color' => $f->fiber_color, + 'fiber_color_hex' => $f->fiber_color_hex, + 'bundle_nr' => $f->bundle_nr, + 'bundle_color' => $f->bundle_color, + 'bundle_color_hex' => $f->bundle_color_hex, 'branch_type' => $f->branch_type, 'branch_cable_nr' => $f->branch_cable_nr, 'branch_from_location' => $f->branch_from_location, @@ -346,26 +568,6 @@ class PopController extends mfBaseController ]; } - $branchPoints = []; - $sql = "SELECT id, description as name, gps_lat, gps_long, object_type, 'dispatcher' as type - FROM FiberPlanDispatcher - WHERE network_id = 90 AND object_type = 4 - AND gps_lat IS NOT NULL AND gps_long IS NOT NULL"; - $res = $db->query($sql); - - if ($db->num_rows($res)) { - while ($data = $db->fetch_array($res)) { - $branchPoints[] = [ - 'id' => $data['id'], - 'name' => $data['name'], - 'gps_lat' => $data['gps_lat'], - 'gps_long' => $data['gps_long'], - 'object_type' => intval($data['object_type']), - 'type' => $data['type'] - ]; - } - } - $details['cable_info'] = [ 'id' => $mainCable->id, 'description' => $mainCable->description, @@ -375,139 +577,54 @@ class PopController extends mfBaseController 'cable_route_full' => $cable_route_data, 'coordinates' => $mainCable->coordinates, 'location' => $mainFiber->location, - 'branch_points' => $branchPoints + 'branch_points' => $globalBranchPoints ]; + $allCablesForMatching = []; foreach ($cableChain as $chainItem) { - $cable = $chainItem['cable']; - - $coords = $cable->coordinates; + $c = $chainItem['cable']; + $coords = $c->coordinates; if (is_string($coords)) { $coords = json_decode($coords, true); } - if ($coords && is_array($coords) && count($coords) > 0) { $allCablesForMatching[] = [ - 'id' => $cable->id, - 'description' => $cable->description, + 'id' => $c->id, + 'description' => $c->description, 'coordinates' => $coords ]; } } - $sql = "SELECT id, description, coordinates - FROM FiberPlanCable - WHERE network_id = 90 - AND coordinates IS NOT NULL - AND coordinates != '' - AND coordinates != '[]'"; - $res = $db->query($sql); - - while ($cableData = $db->fetch_array($res)) { - $coords = json_decode($cableData['coordinates'], true); - - if ($coords && is_array($coords) && count($coords) > 0) { - $exists = false; - foreach ($allCablesForMatching as $existing) { - if ($existing['id'] == $cableData['id']) { - $exists = true; - break; - } - } - - if (!$exists) { - $allCablesForMatching[] = [ - 'id' => $cableData['id'], - 'description' => $cableData['description'], - 'coordinates' => $coords - ]; + foreach ($globalCablesWithCoords as $gc) { + $exists = false; + foreach ($allCablesForMatching as $existing) { + if ($existing['id'] == $gc['id']) { + $exists = true; + break; } } + if (!$exists) { + $allCablesForMatching[] = $gc; + } } $details['all_cables'] = $allCablesForMatching; - $this->log->debug("Hausanschluss-Matching: " . count($allCablesForMatching) . " Kabel verfügbar"); } if (count($cableChain) > 1) { $details['branch_path'] = $this->buildBranchPathFromChain($cableChain, 1, $debug); } - } else { - $this->log->debug("=== MODUS: Vorwärts-Trace (von fiber_id) ==="); + $details['debug'] = $debug; - if ($fiber->cable_id) { - $cable = new FiberPlanCable($fiber->cable_id); - if ($cable->id) { - $cable_route_data = FiberPlanCableModel::getCableRoute($cable->id); - $cable_route_array = []; - foreach ($cable_route_data as $station) { - $cable_route_array[] = $station['name']; - } - - $branchPoints = []; - $sql = "SELECT id, description as name, gps_lat, gps_long, object_type, 'dispatcher' as type - FROM FiberPlanDispatcher - WHERE network_id = 90 AND object_type = 4 - AND gps_lat IS NOT NULL AND gps_long IS NOT NULL"; - $res = $db->query($sql); - - if ($db->num_rows($res)) { - while ($data = $db->fetch_array($res)) { - $branchPoints[] = [ - 'id' => $data['id'], - 'name' => $data['name'], - 'gps_lat' => $data['gps_lat'], - 'gps_long' => $data['gps_long'], - 'object_type' => intval($data['object_type']), - 'type' => $data['type'] - ]; - } - } - - $allFibers = FiberPlanFiberModel::getByCableAndSheet($cable->id, null); - $fibersArray = []; - foreach ($allFibers as $f) { - $fibersArray[] = [ - 'id' => $f->id, - 'fiber_nr_cable' => $f->fiber_nr_cable, - 'branch_type' => $f->branch_type, - 'branch_cable_nr' => $f->branch_cable_nr, - 'branch_from_location' => $f->branch_from_location, - 'branch_fiber_nr' => $f->branch_fiber_nr - ]; - } - - $details['cable_info'] = [ - 'id' => $cable->id, - 'description' => $cable->description, - 'fibers' => $fibersArray, - 'diameter' => $cable->diameter, - 'cable_route_array' => $cable_route_array, - 'cable_route_full' => $cable_route_data, - 'coordinates' => $cable->coordinates, - 'location' => $fiber->location, - 'branch_points' => $branchPoints - ]; - - if ($cable->cable_route) { - $routeArray = json_decode($cable->cable_route, true); - if (is_array($routeArray)) { - $details['cable_info']['cable_route_array'] = $routeArray; - } - } - } - } - - if ($fiber->branch_type === 'Abzweigkabel' && $fiber->branch_cable_nr) { - $details['branch_path'] = $this->traceBranchPath($fiber, 0, 10, $debug); - } + $allPaths[] = [ + 'fiber' => $details + ]; } - $details['debug'] = $debug; - - return mfBaseController::returnJson(mfResponse::Ok(['fiber' => $details])); + return mfBaseController::returnJson(mfResponse::Ok(['paths' => $allPaths])); } private function buildCompleteCableChain($endFiber) @@ -530,10 +647,13 @@ class PopController extends mfBaseController ]); while ($depth < $maxDepth) { + $currentFiberNr = intval($currentFiber->fiber_nr_cable); + $sql = "SELECT * FROM FiberPlanFiber - WHERE branch_type = 'Abzweigkabel' - AND branch_cable_nr = '" . $db->escape($currentCable->description) . "' - LIMIT 1"; + WHERE branch_type = 'Abzweigkabel' + AND branch_cable_nr = '" . $db->escape($currentCable->description) . "' + AND branch_fiber_nr = $currentFiberNr + LIMIT 1"; $this->log->debug("Depth $depth: Suche Parent-Faser für Kabel: {$currentCable->description}"); @@ -1267,6 +1387,9 @@ class PopController extends mfBaseController case "getFiberPath": return $this->getFiberPathAction(); break; + case "getAllFiberPathsForHome": + return $this->getAllFiberPathsForHomeAction(); + break; case "saveCableFibers": return $this->saveCableFibersAction(); break; @@ -1276,6 +1399,9 @@ class PopController extends mfBaseController case "getNetworkMapData": return $this->getNetworkMapDataAction(); break; + case "getSplicePlanForElement": + return $this->getSplicePlanForElementAction(); + break; default: $return = false; } @@ -1459,7 +1585,7 @@ class PopController extends mfBaseController $cables = []; $cableRes = $db->select( "FiberPlanCable", - "id, description, fibers, diameter, state, coordinates", + "id, description, fibers, diameter, state, coordinates, level, cable_type, status", "network_id=$network_id" ); @@ -1491,7 +1617,10 @@ class PopController extends mfBaseController 'coordinates' => $convertedCoords, 'fibers' => $cableData->fibers, 'diameter' => $cableData->diameter, - 'state' => $cableData->state + 'state' => $cableData->state, + 'level' => $cableData->level, + 'cable_type' => $cableData->cable_type, + 'status' => $cableData->status ]; } } @@ -1648,4 +1777,126 @@ class PopController extends mfBaseController 'customerConnections' => $customerConnections ])); } + + protected function getSplicePlanForElementAction() + { + $id = $this->request->id; + if (!is_numeric($id)) { + return mfBaseController::returnJson(mfResponse::BadRequest(['message' => 'Invalid ID'])); + } + + $db = FronkDB::singleton(); + + $dispatcherRes = $db->select("FiberPlanDispatcher", "*", "id=$id"); + if (!$db->num_rows($dispatcherRes)) { + return mfBaseController::returnJson(mfResponse::NotFound(['message' => 'Verteiler nicht gefunden'])); + } + $dispatcher = $db->fetch_object($dispatcherRes); + $dispatcherName = $dispatcher->description; + + $cableIds = []; + $sql = "SELECT DISTINCT cable_id FROM FiberPlanCableStation WHERE station_type='dispatcher' AND station_id=$id"; + $res = $db->query($sql); + if ($db->num_rows($res)) { + while ($row = $db->fetch_array($res)) { + $cableIds[] = $row['cable_id']; + } + } + + $result = []; + + if (!empty($cableIds)) { + $cableIdsStr = implode(',', $cableIds); + + $cableMap = []; + $cableRes = $db->select("FiberPlanCable", "id, description", "id IN ($cableIdsStr)"); + while ($c = $db->fetch_object($cableRes)) { + $cableMap[$c->id] = $c->description; + } + + $escapedName = $db->escape($dispatcherName); + $sqlFibers = "SELECT * FROM FiberPlanFiber WHERE cable_id IN ($cableIdsStr) AND (branch_from_location = '$escapedName' OR location = '$escapedName')"; + + $fiberRes = $db->query($sqlFibers); + + $rawFibers = []; + $targetCableNames = []; + + while ($fiber = $db->fetch_object($fiberRes)) { + $rawFibers[] = $fiber; + if ($fiber->branch_cable_nr) { + $targetCableNames[$fiber->branch_cable_nr] = true; + } + } + + $targetColorMap = []; + + if (!empty($targetCableNames)) { + $namesList = []; + foreach (array_keys($targetCableNames) as $name) { + $namesList[] = "'" . $db->escape($name) . "'"; + } + $namesStr = implode(',', $namesList); + $targetCablesRes = $db->select("FiberPlanCable", "id, description", "description IN ($namesStr)"); + $targetCableIds = []; + $targetCableIdToName = []; + while ($tc = $db->fetch_object($targetCablesRes)) { + $targetCableIds[] = $tc->id; + $targetCableIdToName[$tc->id] = $tc->description; + } + + if (!empty($targetCableIds)) { + $tcIdsStr = implode(',', $targetCableIds); + $targetFibersRes = $db->select("FiberPlanFiber", "cable_id, fiber_nr_cable, fiber_color, fiber_color_hex", "cable_id IN ($tcIdsStr)"); + while ($tf = $db->fetch_object($targetFibersRes)) { + $cName = $targetCableIdToName[$tf->cable_id] ?? null; + if ($cName) { + $targetColorMap[$cName][$tf->fiber_nr_cable] = [ + 'color' => $tf->fiber_color, + 'hex' => $tf->fiber_color_hex + ]; + } + } + } + } + + foreach ($rawFibers as $fiber) { + $targetColorInfo = null; + if ($fiber->branch_cable_nr && $fiber->branch_fiber_nr) { + $targetColorInfo = $targetColorMap[$fiber->branch_cable_nr][$fiber->branch_fiber_nr] ?? null; + } + + $result[] = [ + 'cable_name' => $cableMap[$fiber->cable_id] ?? 'Unknown', + 'fiber_nr' => $fiber->fiber_nr_cable, + 'fiber_color' => $fiber->fiber_color, + 'fiber_color_hex' => $fiber->fiber_color_hex, + 'bundle_color' => $fiber->bundle_color, + 'bundle_color_hex' => $fiber->bundle_color_hex, + + 'target_cable' => $fiber->branch_cable_nr, + 'target_fiber' => $fiber->branch_fiber_nr, + 'target_fiber_color' => $targetColorInfo['color'] ?? null, + 'target_fiber_color_hex' => $targetColorInfo['hex'] ?? null, + 'target_bundle_color' => $fiber->branch_bundle_color, + 'target_bundle_color_hex' => $fiber->branch_bundle_color_hex, + + 'connector' => $fiber->connector_nr, + 'description' => $fiber->comment, + 'home_id' => $fiber->home_id, + 'address' => $fiber->address ?? null, + 'customer_cable_type' => $fiber->customer_cable_type ?? null, + 'customer_cable_fiber_nr' => $fiber->customer_cable_fiber_nr ?? null, + 'customer_connector_type' => $fiber->customer_connector_type ?? null, + 'customer_cable_spec' => $fiber->customer_cable_spec ?? null, + 'customer_fiber_range' => $fiber->customer_fiber_range ?? null, + ]; + } + } + + return mfBaseController::returnJson(mfResponse::Ok([ + 'dispatcher' => $dispatcher, + 'connections' => $result + ])); + } } \ No newline at end of file diff --git a/public/img/markers/marker-pop-b.png b/public/img/markers/marker-pop-b.png new file mode 100644 index 000000000..e0d6262c8 Binary files /dev/null and b/public/img/markers/marker-pop-b.png differ diff --git a/public/img/markers/marker-pop-bl.png b/public/img/markers/marker-pop-bl.png new file mode 100644 index 000000000..51b0e3849 Binary files /dev/null and b/public/img/markers/marker-pop-bl.png differ diff --git a/public/img/markers/marker-pop-o.png b/public/img/markers/marker-pop-o.png new file mode 100644 index 000000000..c57d8095b Binary files /dev/null and b/public/img/markers/marker-pop-o.png differ diff --git a/public/img/markers/marker-pop-v.png b/public/img/markers/marker-pop-v.png new file mode 100644 index 000000000..9df7a59d6 Binary files /dev/null and b/public/img/markers/marker-pop-v.png differ diff --git a/public/js/pages/Pop/Pop.css b/public/js/pages/Pop/Pop.css new file mode 100644 index 000000000..0dff802dd --- /dev/null +++ b/public/js/pages/Pop/Pop.css @@ -0,0 +1,10 @@ +.fa-map-location-dot:before +{ + color: #d80000; +} +.fa-map-location-dot:after +{ + color: #147d00; + opacity: 0.9; + +} \ No newline at end of file diff --git a/public/js/pages/Pop/PopMap.js b/public/js/pages/Pop/PopMap.js new file mode 100644 index 000000000..a0409cabd --- /dev/null +++ b/public/js/pages/Pop/PopMap.js @@ -0,0 +1,353 @@ +Vue.component('pop-map-modal', { + template: ` +
+ +
+ `, + data() { + return { + map: null, + popLayer: null, + searchQuery: '', + filteredPops: [], + showSuggestions: false, + selectedIndex: -1, + categories: { + 1: 'Outdoor (Kasten/Schrank)', + 2: 'Indoor (Keller Gebäude)', + 3: 'Sender/Funk (Sendemast)', + 4: 'Container (Garage, Container)', + 99: 'Unbekannt' + }, + states: { + 1: "Planung (Innenleben)", + 2: "Bauphase (Schrank)", + 3: "Grobdoku", + 4: "in Betrieb", + 5: "von Techniker abgenommen (Altbestand)" + }, + categoryImages: { + 1: 'img/markers/marker-pop.png', + 2: 'img/markers/marker-pop-o.png', + 3: 'img/markers/marker-pop-b.png', + 4: 'img/markers/marker-pop-v.png', + 99: 'img/markers/marker-pop-bl.png' + }, + categoryColors: { + 1: '#a1dfa0', // Outdoor - Green + 2: '#f8b767', // Indoor - Orange + 3: '#a9b8ec', // Sender - Blue + 4: '#f89797', // Container - Yellow + 99: '#808080' // Unbekannt - Gray + }, + visibleCategories: { + 1: true, + 2: true, + 3: true, + 4: true, + 99: true + }, + categoryCounts: { + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 99: 0 + }, + allPops: [], + markers: [] + }; + }, + mounted() { + // Prepare data + const popsObj = window.TT_CONFIG.POPS || {}; + this.allPops = Object.values(popsObj); + + this.calculateCounts(); + + // Listen to modal open event to init map correctly (fix render issues) + $(document).on('shown.bs.modal', '#popMapModal', this.initMap); + + // Close suggestions when clicking outside + document.addEventListener('click', this.handleClickOutside); + }, + beforeDestroy() { + $(document).off('shown.bs.modal', '#popMapModal', this.initMap); + document.removeEventListener('click', this.handleClickOutside); + }, + methods: { + calculateCounts() { + // Reset counts + for (let key in this.categoryCounts) { + this.categoryCounts[key] = 0; + } + + this.allPops.forEach(pop => { + const category = pop.category || 99; + if (this.categoryCounts.hasOwnProperty(category)) { + this.categoryCounts[category]++; + } else { + // Just in case we have a category not in our list, count it as 99 or ignore + this.categoryCounts[99]++; + } + }); + }, + open() { + $('#popMapModal').modal('show'); + }, + initMap() { + if (this.map) { + setTimeout(() => { + this.map.invalidateSize(); + }, 100); + return; + } + + if (typeof L === 'undefined' || !L.MakiMarkers) { + console.error('Leaflet or MakiMarkers not loaded'); + return; + } + + L.MakiMarkers.accessToken = window.TT_CONFIG.MAPBOX_TOKEN; + + this.map = L.map('pop-map').setView([51.1657, 10.4515], 6); + + const standardLayer = L.tileLayer('https://mapsneu.wien.gv.at/basemap/{id}/normal/google3857/{z}/{y}/{x}.{imgtype}', { + maxZoom: 19, + id: "geolandbasemap", + imgtype: "png", + attribution: 'Basemap.at' + }); + + const satelliteLayer = L.tileLayer('https://mapsneu.wien.gv.at/basemap/{id}/normal/google3857/{z}/{y}/{x}.{imgtype}', { + maxZoom: 19, + id: "bmaporthofoto30cm", + imgtype: "jpeg", + attribution: 'Basemap.at' + }); + + standardLayer.addTo(this.map); + + const baseMaps = { + "Karte": standardLayer, + "Satellit": satelliteLayer + }; + + L.control.layers(baseMaps).addTo(this.map); + + this.popLayer = L.featureGroup().addTo(this.map); + + this.updateMap(); + }, + updateMap(shouldFit = true) { + if (!this.map) return; + + this.popLayer.clearLayers(); + this.markers = []; + + const bounds = L.latLngBounds(); + let hasMarkers = false; + + this.allPops.forEach(pop => { + const category = pop.category || 99; + + if (!this.visibleCategories[category]) return; + + const gps = pop.gps; + if (!gps) return; + + const parts = gps.split(','); + if (parts.length !== 2) return; + + const lat = parseFloat(parts[0]); + const lng = parseFloat(parts[1]); + + if (isNaN(lat) || isNaN(lng) || (lat === 0 && lng === 0)) return; + + let iconUrl = this.categoryImages[category] || this.categoryImages[99]; + let color = this.categoryColors[category] || '#808080'; + + const marker = L.marker([lat, lng], { + icon: L.MakiMarkers.icon({ + icon: 'village', + color: color, + size: 'l' + }) + }); + + let categoryName = this.categories[category] || 'Unbekannt'; + let stateText = this.states[pop.state] || pop.state || '-'; + + const popupContent = ` +
+
${pop.name}
+
+
Kategorie: ${categoryName}
+
Status: ${stateText}
+
Zutritt: ${pop.location || '-'}
+
+ Details +
+
+ `; + + marker.bindPopup(popupContent); + marker.popData = pop; + + this.popLayer.addLayer(marker); + this.markers.push(marker); + bounds.extend([lat, lng]); + hasMarkers = true; + }); + + if (shouldFit === true && hasMarkers && !this.searchQuery) { + this.map.fitBounds(bounds, {padding: [50, 50]}); + } + }, + filterPops() { + const query = this.searchQuery.toLowerCase().trim(); + this.selectedIndex = -1; + + if (query.length < 1) { + this.filteredPops = []; + this.showSuggestions = false; + return; + } + + this.filteredPops = this.allPops.filter(pop => + pop.name.toLowerCase().includes(query) || + (pop.location && pop.location.toLowerCase().includes(query)) + ).slice(0, 10); + + this.showSuggestions = true; + }, + moveSelection(step) { + if (!this.showSuggestions || this.filteredPops.length === 0) return; + + this.selectedIndex += step; + + if (this.selectedIndex < 0) { + this.selectedIndex = this.filteredPops.length - 1; + } else if (this.selectedIndex >= this.filteredPops.length) { + this.selectedIndex = 0; + } + }, + handleEnter() { + if (this.showSuggestions && this.selectedIndex >= 0 && this.selectedIndex < this.filteredPops.length) { + this.selectPop(this.filteredPops[this.selectedIndex]); + } else { + this.searchPop(); + } + }, + selectPop(pop) { + this.searchQuery = pop.name; + this.showSuggestions = false; + this.selectedIndex = -1; + this.searchPop(); + }, + handleClickOutside(event) { + if (!event.target.closest('.input-group')) { + this.showSuggestions = false; + this.selectedIndex = -1; + } + }, + searchPop() { + const query = this.searchQuery.toLowerCase().trim(); + if (!query) { + this.clearSearch(); + return; + } + + this.showSuggestions = false; + this.selectedIndex = -1; + + let found = this.markers.find(m => m.popData.name.toLowerCase().includes(query)); + + if (!found) { + const hiddenPop = this.allPops.find(p => p.name.toLowerCase().includes(query)); + if (hiddenPop) { + const category = hiddenPop.category || 99; + if (!this.visibleCategories[category]) { + this.visibleCategories[category] = true; + this.updateMap(false); + found = this.markers.find(m => m.popData.id === hiddenPop.id); + } + } + } + + if (found) { + this.map.flyTo(found.getLatLng(), 15); + setTimeout(() => { + found.openPopup(); + }, 500); + } else { + alert('Kein POP gefunden (oder keine GPS Koordinaten).'); + } + }, + clearSearch() { + this.searchQuery = ''; + this.filteredPops = []; + this.showSuggestions = false; + this.selectedIndex = -1; + const bounds = L.latLngBounds(); + this.markers.forEach(m => bounds.extend(m.getLatLng())); + if (this.markers.length > 0) { + this.map.fitBounds(bounds, {padding: [50, 50]}); + } + } + } +}); \ No newline at end of file diff --git a/public/js/pages/Pop/Pop.js b/public/js/pages/Pop/PopView.js similarity index 88% rename from public/js/pages/Pop/Pop.js rename to public/js/pages/Pop/PopView.js index d4b337192..5d359b1e9 100644 --- a/public/js/pages/Pop/Pop.js +++ b/public/js/pages/Pop/PopView.js @@ -11,12 +11,19 @@ Vue.component('Pop', { Pop hinzufügen + + + @@ -45,6 +52,7 @@ Vue.component('Pop', { + `, data() { @@ -60,7 +68,8 @@ Vue.component('Pop', { {value: '1', text: 'Outdoor (Kasten/Schrank)'}, {value: '2', text: 'Indoor (Keller Gebäude)'}, {value: '3', text: 'Sender/Funk (Sendemast)'}, - {value: '4', text: 'Container (Garage, Container)'}]}, + {value: '4', text: 'Container (Garage, Container)'}, + {value: '99', text: 'Unbekannt'}]}, {text: 'Netzgebiet', key: 'networkArea', class: 'text-center', // TODO: fix autocomplete Filter // filter: 'autocomplete', diff --git a/public/js/pages/pop/detail.js b/public/js/pages/Pop/detail/detail.js similarity index 100% rename from public/js/pages/pop/detail.js rename to public/js/pages/Pop/detail/detail.js diff --git a/public/js/pages/Pop/fiber.js b/public/js/pages/Pop/detail/fiber.js similarity index 85% rename from public/js/pages/Pop/fiber.js rename to public/js/pages/Pop/detail/fiber.js index 20ce6510f..2b7a18d4a 100644 --- a/public/js/pages/Pop/fiber.js +++ b/public/js/pages/Pop/detail/fiber.js @@ -71,7 +71,7 @@ $(document).ready(function () { if (!$menu.length) return; $menu.removeAttr('x-placement'); if ($menu.parent()[0] !== document.body) $menu.detach().appendTo('body'); - $menu.css({ display: 'block' }).addClass('show'); + $menu.css({display: 'block'}).addClass('show'); const el = $menu[0]; const w = $menu.outerWidth(), h = $menu.outerHeight(); const vw = window.innerWidth, vh = window.innerHeight; @@ -174,7 +174,7 @@ $(document).ready(function () { case 'delete': if (confirm("Kabel '" + cableName + "' wirklich entfernen?")) { - $.post(linkRemoveCable, { id: cableId }, function (response) { + $.post(linkRemoveCable, {id: cableId}, function (response) { if (response.success) { let currentSide = currentCableElement.closest('tbody').data('side'); updateAllRackViews(rackid, currentSide); @@ -308,7 +308,7 @@ $(document).ready(function () { initCableSearch(); }); -function loadFiberPlanCableDetails(cableName, fiberStart = null, fiberEnd = null) { +function loadFiberPlanCableDetails(cableName, fiberStart = null, fiberEnd = null, homeId = null) { if (cableName) { cableName = cableName.split(' ')[0]; } @@ -316,7 +316,8 @@ function loadFiberPlanCableDetails(cableName, fiberStart = null, fiberEnd = null const modalBody = $('#fiberPlanCableModalBody'); modalHistory.push({ type: 'cable', - data: cableName + data: cableName, + homeId: homeId }); modalBody.html(` @@ -352,7 +353,7 @@ function loadFiberPlanCableDetails(cableName, fiberStart = null, fiberEnd = null editBtn.attr('data-cable-id', cable.id); editBtn.attr('data-cable-name', cable.description); editBtn.show(); - renderFiberPlanCableDetails(response.result.cable, fiberStart, fiberEnd); + renderFiberPlanCableDetails(response.result.cable, fiberStart, fiberEnd, homeId); } else { $('#modal-edit-cable-btn').hide(); modalBody.html(` @@ -374,9 +375,19 @@ function loadFiberPlanCableDetails(cableName, fiberStart = null, fiberEnd = null }); } -function renderFiberPlanCableDetails(cable, fiberStart = null, fiberEnd = null) { +function renderFiberPlanCableDetails(cable, fiberStart = null, fiberEnd = null, homeId = null) { const modalBody = $('#fiberPlanCableModalBody'); + let displayFibers = cable.fiber_list; + if (homeId) { + displayFibers = displayFibers.filter(f => f.home_id === homeId); + $('#fiberPlanCableModal .modal-title').html(' Fasern für Hausanschluss: ' + homeId + + ''); + } else { + $('#fiberPlanCableModal .modal-title').html(' Kabel-Details' + + ''); + } + const usedFibers = cable.fiber_list.filter(f => f.home_id || f.branch_type).length; const freeFibers = cable.fiber_list.filter(f => !f.home_id && !f.branch_type).length; @@ -486,7 +497,7 @@ function renderFiberPlanCableDetails(cable, fiberStart = null, fiberEnd = null)
- Fasern (${cable.fiber_list.length}) + Fasern (${displayFibers.length})
@@ -506,8 +517,8 @@ function renderFiberPlanCableDetails(cable, fiberStart = null, fiberEnd = null) `; - if (cable.fiber_list.length > 0) { - cable.fiber_list.forEach(fiber => { + if (displayFibers.length > 0) { + displayFibers.forEach(fiber => { const colorStyle = fiber.fiber_color_hex ? `style="background-color: ${fiber.fiber_color_hex}"` : ''; const statusBadge = getFiberStatusBadge(fiber); const info = getFiberShortInfo(fiber); @@ -517,7 +528,7 @@ function renderFiberPlanCableDetails(cable, fiberStart = null, fiberEnd = null) ${(fiber.bundle_color ? '' : '') || (fiber.bundle_nr ? '' : '-')}` : '-'; // const hasPath = fiber.branch_type === 'Abzweigkabel' || fiber.home_id; - const hasPath = fiber.home_id && fiber.home_id !== '' || fiber.final_home_id ; + const hasPath = fiber.home_id && fiber.home_id !== '' || fiber.final_home_id; const actionButton = hasPath ? ` + + + + + + + `; + + $('#fiberRouteMapModal').remove(); + + const fsElement = document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement; + if (fsElement) { + $(fsElement).append(mapModalHtml); + } else { + $('body').append(mapModalHtml); + } + + $('#fiberRouteMapModal').modal('show'); + + $('#fiber-route-map').html(` +
+
+

Lade Strecken...

+
+ `); + + $.ajax({ + url: linkGetAllFiberPathsForHome, + method: 'GET', + data: {home_id: homeId}, + dataType: 'json', + success: function (response) { + if (response.status === 'OK' && response.result.paths) { + initializeMultiFiberMap(response.result.paths); + renderMultiCableRouteSchema(response.result.paths); + } else { + $('#fiber-route-map').html(` +
+ + Keine Strecken gefunden. +
+ `); + } + }, + error: function () { + $('#fiber-route-map').html(` +
+ + Fehler beim Laden der Daten. +
+ `); + } + }); +} + +function initializeMultiFiberMap(paths) { + setTimeout(function () { + // 1. Alte Karte sauber entfernen + if (window.fiberRouteMapInstance) { + window.fiberRouteMapInstance.remove(); + window.fiberRouteMapInstance = null; + } + + const mapContainer = document.getElementById('fiber-route-map'); + if (!mapContainer) return; + + mapContainer.innerHTML = ''; // Spinner entfernen + + const allBounds = L.featureGroup(); + let hasData = false; + + // 2. Neue Karte initialisieren + const map = L.map('fiber-route-map'); + window.fiberRouteMapInstance = map; + + L.tileLayer('https://mapsneu.wien.gv.at/basemap/{id}/normal/google3857/{z}/{y}/{x}.{imgtype}', { + maxZoom: 22, + id: "geolandbasemap", + imgtype: "png" + }).addTo(map); + + // 3. Daten durchlaufen und zeichnen + paths.forEach((path, pathIndex) => { + const fiber = path.fiber; + + // WICHTIG: Hier holen wir die Kabeldaten. + // Entweder aus 'all_cables' (neues PHP) oder 'chain' (altes Fallback) + let segmentsToDraw = []; + + if (fiber && fiber.all_cables) { + segmentsToDraw = fiber.all_cables; + } else if (path.chain) { + segmentsToDraw = path.chain; + } + + // Zufallsfarbe oder basierend auf Index, falls keine Farbe da ist + const defaultColor = ['#3388ff', '#ff3333', '#33ff33', '#ffaa00'][pathIndex % 4]; + + segmentsToDraw.forEach(segment => { + let routePoints = []; + + // Koordinaten parsen + if (segment.coordinates) { + let coords = segment.coordinates; + if (typeof coords === 'string') { + try { coords = JSON.parse(coords); } catch (e) {} + } + if (Array.isArray(coords) && coords.length > 0) { + routePoints = coords.map(c => [parseFloat(c.gps_lat), parseFloat(c.gps_long)]); + } + } + + if (routePoints.length > 0) { + hasData = true; + + // Linie zeichnen + const polyline = L.polyline(routePoints, { + color: fiber.fiber_color_hex || defaultColor, + weight: 4, + opacity: 0.7 + }).bindTooltip(` + ${segment.description || segment.cable_name || 'Kabel'}
+ Faser: ${fiber.fiber_nr_cable} (${fiber.fiber_color}) + `, { sticky: true }); + + allBounds.addLayer(polyline); + } + }); + + // Stationen (Verteiler/POP) zeichnen, falls vorhanden (im Hauptkabel-Info) + if (fiber.cable_info && fiber.cable_info.cable_route_full) { + fiber.cable_info.cable_route_full.forEach(station => { + if (station.gps_lat && station.gps_long) { + const lat = parseFloat(station.gps_lat); + const lng = parseFloat(station.gps_long); + + // Icon Logik + let iconColor = '#3388ff'; + let iconName = 'circle'; + + if (station.type === 'pop') { iconColor = '#acf0ab'; iconName = 'village'; } + else if (station.object_type == 4) { iconColor = '#FF6B6B'; iconName = 'cross'; } // Abzweig + else if (station.object_type == 2) { iconColor = '#ffa726'; iconName = 'square'; } // Schacht + + const marker = L.marker([lat, lng], { + icon: L.MakiMarkers.icon({ icon: iconName, color: iconColor, size: 's' }) + }).bindPopup(`${station.name}`); + + allBounds.addLayer(marker); + } + }); + } + }); + + if (hasData) { + allBounds.addTo(map); + map.fitBounds(allBounds.getBounds(), { padding: [50, 50] }); + } else { + // Fallback: Wenn keine Geodaten da sind, leere Karte auf Wien zentrieren + map.setView([48.2082, 16.3738], 12); + // Optional: Nachricht anzeigen, aber Karte nicht killen + // L.popup().setLatLng([48.2082, 16.3738]).setContent("Keine GPS-Daten für diese Fasern.").openOn(map); + } + + }, 300); // Timeout lassen, damit das Modal sicher offen ist +} + +function renderMultiCableRouteSchema(paths) { + const container = $('#fiber-route-schema'); + container.empty(); + + if (!paths || paths.length === 0) { + container.html('
Keine Daten verfügbar
'); + return; + } + + paths.forEach((path, pathIndex) => { + const fiber = path.fiber; + const chain = path.chain; // Chain is [POP-Side ... Home-Side] + + let html = ` +
+
+ + Faser #${fiber.fiber_nr_cable} + (Bündel ${fiber.bundle_nr}) - HomeID: ${fiber.home_id || '?'} +
+
+ `; + + // 1. Start Node: POP + // Attempt to find POP name from the first segment's route + let popName = 'POP'; + if (chain.length > 0 && chain[0].cable_route_full) { + const popStation = chain[0].cable_route_full.find(s => s.type === 'pop'); + if (popStation) popName = popStation.name; + } + + html += ` +
+
+ +
+
+ ${popName} +
+
+ `; + + chain.forEach((segment, segIndex) => { + const isLast = segIndex === chain.length - 1; + const segmentColor = segment.fiber_color_hex || '#666'; + const fiberInfo = `F${segment.fiber_nr} / B${segment.bundle_nr}`; + + const lineClass = (segment.type === 'branch') ? 'schema-line-dashed-horizontal' : 'schema-line-horizontal'; + + // Cable Line + html += ` +
+
+
+ ${segment.cable_name}
+ ${fiberInfo} +
+
+
+ `; + + // Next Node + let nodeClass = 'station-distributor'; + let icon = 'fa-sitemap'; + let iconColor = '#3388ff'; + let label = 'Verteiler'; + + if (isLast) { + // Destination of the last cable is the Home + nodeClass = 'station-customer'; + icon = 'fa-home'; + iconColor = '#c62828'; + label = 'Haus'; + if (fiber.address) { + label += `
${fiber.address}`; + } + } else { + // Intermediate Node (Distributor/Branch) + // We check the next segment to see where this one ends, + // or check this segment's destination info if available. + // Often the cable route of the current segment ends at the distributor where the next segment starts. + // We can try to get the name of the last station of this cable. + + if (segment.cable_route_full && segment.cable_route_full.length > 0) { + const lastStation = segment.cable_route_full[segment.cable_route_full.length - 1]; + if (lastStation) { + label = lastStation.name; + if (lastStation.object_type == 4) { + nodeClass = 'station-branch'; + icon = 'fa-code-branch'; + iconColor = '#b71c1c'; + } else if (lastStation.object_type == 2) { + nodeClass = 'station-manhole'; + icon = 'fa-square'; + iconColor = '#e65100'; + } + } + } + + if (segment.type === 'branch' && nodeClass === 'station-distributor') { + // Fallback for branch cable end if not clear + nodeClass = 'station-branch'; + icon = 'fa-code-branch'; + iconColor = '#b71c1c'; + label = 'Abzweig'; + } + } + + html += ` +
+
+ +
+
+ ${label} +
+
+ `; + }); + + html += ` +
+
+ `; + + container.append(html); + }); +} + + function loadFiberPath(fiberId) { const modal = $('#fiberPlanCableModal'); const modalBody = $('#fiberPlanCableModalBody'); @@ -627,7 +963,7 @@ function loadFiberPath(fiberId) { $.ajax({ url: linkGetFiberPath, method: 'GET', - data: { fiber_id: fiberId }, + data: {fiber_id: fiberId}, dataType: 'json', success: function (response) { if (response.status === 'OK' && response.result.fiber) { @@ -1155,7 +1491,7 @@ function showCableRouteMap(cableId, cableName) { $.ajax({ url: linkGetCableDetails, method: 'GET', - data: { cable_name: cableName }, + data: {cable_name: cableName}, dataType: 'json', success: function (response) { if (response.status === 'OK' && response.result.cable) { @@ -1421,9 +1757,9 @@ function initializeCableRouteMap(cable) { }); } - map.fitBounds(markerGroup.getBounds(), { padding: [50, 50] }); + map.fitBounds(markerGroup.getBounds(), {padding: [50, 50]}); - const legend = L.control({ position: 'bottomright' }); + const legend = L.control({position: 'bottomright'}); legend.onAdd = function (map) { const div = L.DomUtil.create('div', 'info legend'); div.style.backgroundColor = 'white'; @@ -1448,6 +1784,7 @@ function initializeCableRouteMap(cable) { }, 300); } + function renderCableRouteSchema(cable) { const schemaContainer = $('#cable-route-schema'); @@ -1461,14 +1798,14 @@ function renderCableRouteSchema(cable) { html += '
'; cable.cable_route_full.forEach((station, index) => { - let style = { icon: 'fa-sitemap', cssClass: 'station-distributor', color: '#3388ff', typeLabel: 'Verteiler' }; + let style = {icon: 'fa-sitemap', cssClass: 'station-distributor', color: '#3388ff', typeLabel: 'Verteiler'}; let badgeClass = 'badge-info'; if (station.type === 'pop') { - style = { icon: 'fa-building', cssClass: 'station-pop', color: '#4caf50', typeLabel: 'POP' }; + style = {icon: 'fa-building', cssClass: 'station-pop', color: '#4caf50', typeLabel: 'POP'}; badgeClass = 'badge-success'; } else if (station.object_type == 2) { // Schacht - style = { icon: 'fa-archive', cssClass: 'station-manhole', color: '#ffa726', typeLabel: 'Schacht' }; + style = {icon: 'fa-archive', cssClass: 'station-manhole', color: '#ffa726', typeLabel: 'Schacht'}; badgeClass = 'badge-warning'; } @@ -1536,6 +1873,7 @@ function renderCableRouteSchema(cable) { $station.popover('show'); }); } + function showFiberRouteMap(fiberId) { const mapModalHtml = `