- Implement test script for new SAG module endpoints BE-003 (Tag State Management) and BE-004 (Bulk Operations). - Create test cases for creating, updating, and bulk operations on cases and tags. - Add a test for module deactivation to ensure data integrity is maintained. - Include setup and teardown for tests to clear database state before and after each test.
190 lines
7.8 KiB
HTML
190 lines
7.8 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
|
|
|
{% block title %}Lokaliteter kort - BMC Hub{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid px-4 py-4">
|
|
<!-- Breadcrumb -->
|
|
<nav aria-label="breadcrumb" class="mb-4">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="/" class="text-decoration-none">Hjem</a></li>
|
|
<li class="breadcrumb-item"><a href="/app/locations" class="text-decoration-none">Lokaliteter</a></li>
|
|
<li class="breadcrumb-item active">Kort</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Header Section -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<h1 class="h2 fw-700 mb-2">Lokaliteter kort</h1>
|
|
<p class="text-muted small">Interaktivt kort over alle lokationer</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter Section -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="d-flex gap-2">
|
|
<select class="form-select form-select-sm" id="typeFilter" style="max-width: 200px;">
|
|
<option value="">Alle typer</option>
|
|
{% if location_types %}
|
|
{% for type_option in location_types %}
|
|
{% set option_value = type_option['value'] if type_option is mapping else type_option %}
|
|
{% set option_label = type_option['label'] if type_option is mapping else type_option %}
|
|
<option value="{{ option_value }}">
|
|
{% if option_value == 'kompleks' %}Kompleks{% elif option_value == 'bygning' %}Bygning{% elif option_value == 'etage' %}Etage{% elif option_value == 'customer_site' %}Kundesite{% elif option_value == 'rum' %}Rum{% elif option_value == 'vehicle' %}Køretøj{% else %}{{ option_label }}{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</select>
|
|
<button type="button" class="btn btn-primary btn-sm" id="filterBtn">
|
|
<i class="bi bi-funnel me-2"></i>Anvend
|
|
</button>
|
|
<a href="/app/locations" class="btn btn-outline-secondary btn-sm">
|
|
<i class="bi bi-list me-2"></i>Listevisning
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Map Container -->
|
|
<div class="card border-0" style="height: 600px; position: relative;">
|
|
<div id="map" style="width: 100%; height: 100%; border-radius: 12px;"></div>
|
|
</div>
|
|
|
|
<!-- Location Count -->
|
|
<div class="mt-3 text-muted small text-center">
|
|
<span id="locationCount">{{ locations | length }}</span> lokation(er) på kort
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Leaflet CSS & JS -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.css" />
|
|
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.js"></script>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.1/dist/MarkerCluster.css" />
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.1/dist/MarkerCluster.Default.css" />
|
|
<script src="https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.1/dist/leaflet.markercluster.js"></script>
|
|
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
const locationsData = {{ locations | tojson }};
|
|
const locationTypes = {{ location_types | tojson }};
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize map
|
|
const map = L.map('map').setView([55.7, 12.6], 6);
|
|
|
|
// Add tile layer (with dark mode support)
|
|
const isDarkMode = document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
|
const tileLayer = isDarkMode
|
|
? 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
|
: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
|
|
|
L.tileLayer(tileLayer, {
|
|
attribution: '© OpenStreetMap contributors',
|
|
maxZoom: 19,
|
|
filter: isDarkMode ? 'invert(1) hue-rotate(180deg)' : 'none'
|
|
}).addTo(map);
|
|
|
|
// Color mapping for location types
|
|
const typeColors = {
|
|
'kompleks': '#0f4c75',
|
|
'bygning': '#1abc9c',
|
|
'etage': '#3498db',
|
|
'customer_site': '#9b59b6',
|
|
'rum': '#e67e22',
|
|
'vehicle': '#8e44ad'
|
|
};
|
|
|
|
// Create marker cluster group
|
|
const markerClusterGroup = L.markerClusterGroup();
|
|
|
|
// Function to create icon
|
|
function createIcon(type) {
|
|
const color = typeColors[type] || '#6c757d';
|
|
return L.icon({
|
|
iconUrl: `https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-${getColorName(color)}.png`,
|
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
|
iconSize: [25, 41],
|
|
iconAnchor: [12, 41],
|
|
popupAnchor: [1, -34],
|
|
shadowSize: [41, 41]
|
|
});
|
|
}
|
|
|
|
function getColorName(hex) {
|
|
const colorMap = {
|
|
'#0f4c75': 'blue',
|
|
'#f39c12': 'orange',
|
|
'#2eb341': 'green',
|
|
'#9b59b6': 'violet',
|
|
'#8e44ad': 'violet'
|
|
};
|
|
return colorMap[hex] || 'blue';
|
|
}
|
|
|
|
// Add markers
|
|
function addMarkers(filter = null) {
|
|
markerClusterGroup.clearLayers();
|
|
let addedCount = 0;
|
|
|
|
locationsData.forEach(location => {
|
|
if (filter && location.location_type !== filter) return;
|
|
|
|
if (location.latitude && location.longitude) {
|
|
const marker = L.marker([location.latitude, location.longitude], {
|
|
icon: createIcon(location.location_type)
|
|
});
|
|
|
|
const typeLabel = {
|
|
'kompleks': 'Kompleks',
|
|
'bygning': 'Bygning',
|
|
'etage': 'Etage',
|
|
'customer_site': 'Kundesite',
|
|
'rum': 'Rum',
|
|
'vehicle': 'Køretøj'
|
|
}[location.location_type] || location.location_type;
|
|
|
|
const popup = `
|
|
<div class="p-2" style="min-width: 250px;">
|
|
<h6 class="fw-600 mb-2"><a href="/app/locations/${location.id}" class="text-decoration-none">${location.name}</a></h6>
|
|
<p class="small mb-2">
|
|
<span class="badge" style="background-color: ${typeColors[location.location_type] || '#6c757d'}; color: white;">${typeLabel}</span>
|
|
</p>
|
|
<p class="small mb-1"><i class="bi bi-geo-alt me-2"></i>${location.address_city || '—'}</p>
|
|
${location.phone ? `<p class="small mb-1"><i class="bi bi-telephone me-2"></i><a href="tel:${location.phone}" class="text-decoration-none">${location.phone}</a></p>` : ''}
|
|
${location.email ? `<p class="small mb-2"><i class="bi bi-envelope me-2"></i><a href="mailto:${location.email}" class="text-decoration-none">${location.email}</a></p>` : ''}
|
|
<a href="/app/locations/${location.id}" class="btn btn-sm btn-primary mt-2 w-100">Se detaljer</a>
|
|
</div>
|
|
`;
|
|
marker.bindPopup(popup);
|
|
markerClusterGroup.addLayer(marker);
|
|
addedCount++;
|
|
}
|
|
});
|
|
|
|
map.addLayer(markerClusterGroup);
|
|
document.getElementById('locationCount').textContent = addedCount;
|
|
}
|
|
|
|
// Initial load
|
|
addMarkers();
|
|
|
|
// Filter button
|
|
document.getElementById('filterBtn').addEventListener('click', function() {
|
|
const selectedType = document.getElementById('typeFilter').value;
|
|
addMarkers(selectedType || null);
|
|
});
|
|
|
|
// Filter on enter
|
|
document.getElementById('typeFilter').addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
document.getElementById('filterBtn').click();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|