bmc_hub/app/modules/locations/templates/edit.html
Christian 29acdf3e01 Add tests for new SAG module endpoints and module deactivation
- 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.
2026-01-31 23:16:24 +01:00

302 lines
16 KiB
HTML
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.

{% extends "shared/frontend/base.html" %}
{% block title %}Rediger {{ location.name }} - 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"><a href="/app/locations/{{ location.id }}" class="text-decoration-none">{{ location.name }}</a></li>
<li class="breadcrumb-item active">Rediger</li>
</ol>
</nav>
<!-- Header -->
<div class="row mb-4">
<div class="col-12">
<h1 class="h2 fw-700 mb-2">Rediger lokation</h1>
<p class="text-muted small">{{ location.name }}</p>
</div>
</div>
<!-- Error Alert -->
<div id="errorAlert" class="alert alert-danger alert-dismissible fade hide" role="alert">
<strong>Fejl!</strong> <span id="errorMessage"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Luk"></button>
</div>
<!-- Form Card -->
<div class="card border-0 mb-4">
<div class="card-body p-5">
<form id="locationForm" method="POST" action="{{ form_action | default('/app/locations/' ~ location.id ~ '/edit') }}" data-no-intercept="true">
<!-- Section 1: Basic Information -->
<fieldset class="mb-5">
<legend class="h5 fw-600 mb-3">Grundlæggende oplysninger</legend>
<div class="mb-3">
<label for="name" class="form-label">Navn *</label>
<input type="text" class="form-control" id="name" name="name" required maxlength="255" value="{{ location.name }}" placeholder="f.eks. Hovedkontor, Lager Nord">
<small class="form-text text-muted">Lokationens navn eller betegnelse</small>
</div>
<div class="mb-3">
<label for="locationType" class="form-label">Type *</label>
<select class="form-select" id="locationType" name="location_type" required>
<option value="">Vælg type</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 location.location_type == option_value %}selected{% endif %}>
{% 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>
</div>
<div class="mb-3">
<label for="parentLocation" class="form-label">Overordnet lokation</label>
<select class="form-select" id="parentLocation" name="parent_location_id">
<option value="">Ingen (øverste niveau)</option>
{% if parent_locations %}
{% for parent in parent_locations %}
<option value="{{ parent.id }}" {% if location.parent_location_id == parent.id %}selected{% endif %}>
{{ parent.name }}{% if parent.location_type %} ({{ parent.location_type }}){% endif %}
</option>
{% endfor %}
{% endif %}
</select>
<div class="form-text">Bruges til hierarki (fx Bygning → Etage → Rum).</div>
</div>
<div class="mb-3">
<label for="customerId" class="form-label">Kunde (valgfri)</label>
<select class="form-select" id="customerId" name="customer_id">
<option value="">Ingen</option>
{% if customers %}
{% for customer in customers %}
<option value="{{ customer.id }}" {% if location.customer_id == customer.id %}selected{% endif %}>{{ customer.name }}</option>
{% endfor %}
{% endif %}
</select>
<div class="form-text">Valgfri kan knyttes til alle typer.</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="isActive" name="is_active" {% if location.is_active %}checked{% endif %}>
<label class="form-check-label" for="isActive">Lokation er aktiv</label>
</div>
</div>
</fieldset>
<!-- Section 2: Address -->
<fieldset class="mb-5">
<legend class="h5 fw-600 mb-3">Adresse</legend>
<div class="mb-3">
<label for="addressStreet" class="form-label">Vejnavn og nummer</label>
<input type="text" class="form-control" id="addressStreet" name="address_street" value="{{ location.address_street | default('') }}" placeholder="f.eks. Hovedgaden 123">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="addressCity" class="form-label">By</label>
<input type="text" class="form-control" id="addressCity" name="address_city" value="{{ location.address_city | default('') }}" placeholder="f.eks. København">
</div>
<div class="col-md-3 mb-3">
<label for="addressPostal" class="form-label">Postnummer</label>
<input type="text" class="form-control" id="addressPostal" name="address_postal_code" value="{{ location.address_postal_code | default('') }}" placeholder="f.eks. 1000">
</div>
<div class="col-md-3 mb-3">
<label for="addressCountry" class="form-label">Land</label>
<input type="text" class="form-control" id="addressCountry" name="address_country" value="{{ location.address_country | default('DK') }}" placeholder="DK">
</div>
</div>
</fieldset>
<!-- Section 3: Contact Information -->
<fieldset class="mb-5">
<legend class="h5 fw-600 mb-3">Kontaktoplysninger</legend>
<div class="mb-3">
<label for="phone" class="form-label">Telefon</label>
<input type="tel" class="form-control" id="phone" name="phone" value="{{ location.phone | default('') }}" placeholder="f.eks. +45 12 34 56 78">
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" value="{{ location.email | default('') }}" placeholder="f.eks. kontakt@lokation.dk">
</div>
</fieldset>
<!-- Section 4: Coordinates (Advanced) -->
<fieldset class="mb-5">
<legend class="h5 fw-600 mb-3">Koordinater (GPS) <span class="badge bg-secondary">Valgfrit</span></legend>
<p class="text-muted small">Bruges til kortintegration og lokalisering</p>
<div class="row">
<div class="col-md-6 mb-3">
<label for="latitude" class="form-label">Breddegrad</label>
<input type="number" class="form-control" id="latitude" name="latitude" step="0.0001" min="-90" max="90" value="{{ location.latitude | default('') }}" placeholder="f.eks. 55.6761">
<small class="form-text text-muted">-90 til 90</small>
</div>
<div class="col-md-6 mb-3">
<label for="longitude" class="form-label">Længdegrad</label>
<input type="number" class="form-control" id="longitude" name="longitude" step="0.0001" min="-180" max="180" value="{{ location.longitude | default('') }}" placeholder="f.eks. 12.5683">
<small class="form-text text-muted">-180 til 180</small>
</div>
</div>
</fieldset>
<!-- Section 5: Notes -->
<fieldset class="mb-5">
<legend class="h5 fw-600 mb-3">Noter</legend>
<div class="mb-3">
<label for="notes" class="form-label">Noter og kommentarer</label>
<textarea class="form-control" id="notes" name="notes" rows="4" maxlength="500" placeholder="Eventuelle noter eller særlige oplysninger om lokationen">{{ location.notes | default('') }}</textarea>
<small class="form-text text-muted"><span id="charCount">{{ (location.notes | default('')) | length }}</span> / 500 tegn</small>
</div>
</fieldset>
<!-- Form Buttons -->
<div class="d-flex gap-2 justify-content-between align-items-center">
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="bi bi-trash me-2"></i>Slet lokation
</button>
<div class="d-flex gap-2">
<a href="/app/locations/{{ location.id }}" class="btn btn-outline-secondary">Annuller</a>
<button type="submit" class="btn btn-primary" id="submitBtn">
<i class="bi bi-check-lg me-2"></i>Gem ændringer
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-bottom-0">
<h5 class="modal-title">Slet lokation?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Luk"></button>
</div>
<div class="modal-body">
<p class="mb-0">Er du sikker på, at du vil slette <strong>{{ location.name }}</strong>?</p>
<p class="text-muted small mt-2">Denne handling kan ikke fortrydes. Lokationen vil blive soft-deleted og kan gendannes af en administrator.</p>
</div>
<div class="modal-footer border-top-0">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Annuller</button>
<button type="button" class="btn btn-danger btn-sm" id="confirmDeleteBtn">
<i class="bi bi-trash me-2"></i>Slet
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('locationForm');
const errorAlert = document.getElementById('errorAlert');
const submitBtn = document.getElementById('submitBtn');
const notesField = document.getElementById('notes');
const charCount = document.getElementById('charCount');
const deleteModalElement = document.getElementById('deleteModal');
const deleteModal = (window.bootstrap && deleteModalElement) ? new bootstrap.Modal(deleteModalElement) : null;
const locationId = '{{ location.id }}';
// Character counter for notes
notesField.addEventListener('input', function() {
charCount.textContent = this.value.length;
});
// Form submission
form.addEventListener('submit', async function(e) {
if (form.dataset.noIntercept === 'true') {
return;
}
e.preventDefault();
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="bi bi-hourglass-split me-2"></i>Gemmer...';
const formData = new FormData(form);
const data = {
name: formData.get('name'),
location_type: formData.get('location_type'),
parent_location_id: formData.get('parent_location_id') ? parseInt(formData.get('parent_location_id')) : null,
customer_id: formData.get('customer_id') ? parseInt(formData.get('customer_id')) : null,
is_active: formData.get('is_active') === 'on',
address_street: formData.get('address_street'),
address_city: formData.get('address_city'),
address_postal_code: formData.get('address_postal_code'),
address_country: formData.get('address_country'),
phone: formData.get('phone'),
email: formData.get('email'),
latitude: formData.get('latitude') ? parseFloat(formData.get('latitude')) : null,
longitude: formData.get('longitude') ? parseFloat(formData.get('longitude')) : null,
notes: formData.get('notes')
};
try {
const response = await fetch(`/api/v1/locations/${locationId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
window.location.href = `/app/locations/${locationId}`;
} else {
const error = await response.json();
document.getElementById('errorMessage').textContent = error.detail || 'Fejl ved opdatering af lokation';
errorAlert.classList.remove('hide');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="bi bi-check-lg me-2"></i>Gem ændringer';
}
} catch (error) {
console.error('Error:', error);
document.getElementById('errorMessage').textContent = 'En fejl opstod. Prøv igen senere.';
errorAlert.classList.remove('hide');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="bi bi-check-lg me-2"></i>Gem ændringer';
}
});
// Delete location
document.getElementById('confirmDeleteBtn').addEventListener('click', function() {
fetch(`/api/v1/locations/${locationId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(response => {
if (response.ok) {
if (deleteModal) {
deleteModal.hide();
}
setTimeout(() => window.location.href = '/app/locations', 300);
} else {
alert('Fejl ved sletning af lokation');
}
})
.catch(error => {
console.error('Error:', error);
alert('Fejl ved sletning af lokation');
});
});
});
</script>
{% endblock %}