bmc_hub/app/modules/locations/templates/edit.html

440 lines
22 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 extra_css %}
<style>
.relation-card {
border: 1px solid rgba(15, 76, 117, 0.1);
border-radius: 1rem;
background: linear-gradient(180deg, #ffffff 0%, #f8fbfd 100%);
padding: 1rem;
}
.relation-summary {
border: 1px dashed rgba(15, 76, 117, 0.22);
border-radius: 0.85rem;
background: rgba(15, 76, 117, 0.05);
padding: 0.85rem 1rem;
}
.relation-summary.empty {
background: #f8fafc;
border-style: solid;
border-color: rgba(0, 0, 0, 0.08);
}
.relation-path {
font-weight: 600;
color: #1f3b53;
}
.relation-meta {
font-size: 0.8rem;
color: var(--text-secondary);
}
</style>
{% 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 == 'kantine' %}Kantine{% elif option_value == 'moedelokale' %}Mødelokale{% elif option_value == 'vehicle' %}Køretøj{% else %}{{ option_label }}{% endif %}
</option>
{% endfor %}
{% endif %}
</select>
</div>
<div class="relation-card mb-3">
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap mb-3">
<div>
<label for="parentLocation" class="form-label mb-1">Placering i hierarki</label>
<div class="text-muted small">Flyt lokationen ved at vælge en ny overordnet lokation.</div>
</div>
{% if selected_parent %}
<a href="/app/locations/{{ selected_parent.id }}" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-eye me-2"></i>Åbn valgt parent
</a>
{% endif %}
</div>
<div class="mb-3">
<label for="parentLocationSearch" class="form-label small text-muted">Søg overordnet lokation</label>
<input type="text" class="form-control" id="parentLocationSearch" placeholder="Søg efter navn, sti eller type...">
</div>
<div class="mb-3">
<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 }}"
data-path="{{ parent.hierarchy_path }}"
data-type="{{ parent.type_label }}"
data-customer-id="{{ parent.customer_id | default('') }}"
{% if location.parent_location_id == parent.id %}selected{% endif %}>
{{ parent.display_name }}
</option>
{% endfor %}
{% endif %}
</select>
<div class="form-text">Listen viser hele stien, så du ikke skal gætte, hvor lokationen lander.</div>
</div>
<div id="parentSummary" class="relation-summary{% if not selected_parent %} empty{% endif %}">
{% if selected_parent %}
<div class="small text-muted mb-1">Nuværende overordnet lokation</div>
<div class="relation-path">{{ selected_parent.hierarchy_path }}</div>
<div class="relation-meta">{{ selected_parent.type_label }}</div>
{% else %}
<div class="small text-muted">Lokationen ligger i topniveau.</div>
{% endif %}
</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>
<div class="mb-3" id="crossFieldOption" {% if location.location_type != 'rum' %}hidden{% endif %}>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="hasCrossField" name="has_cross_field" {% if location.has_cross_field %}checked{% endif %}>
<label class="form-check-label" for="hasCrossField">Rummet indeholder krydsfelt</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 or '' }}" 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 or '') | 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 }}';
const parentLocationSelect = document.getElementById('parentLocation');
const parentLocationSearch = document.getElementById('parentLocationSearch');
const parentSummary = document.getElementById('parentSummary');
// Character counter for notes
notesField.addEventListener('input', function() {
charCount.textContent = this.value.length;
});
function escapeHtml(value) {
return String(value || '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
function updateParentSummary() {
const selectedOption = parentLocationSelect.options[parentLocationSelect.selectedIndex];
const path = selectedOption?.dataset?.path || '';
const type = selectedOption?.dataset?.type || '';
if (!selectedOption || !selectedOption.value) {
parentSummary.classList.add('empty');
parentSummary.innerHTML = '<div class="small text-muted">Lokationen ligger i topniveau.</div>';
return;
}
parentSummary.classList.remove('empty');
parentSummary.innerHTML = `
<div class="small text-muted mb-1">Valgt overordnet lokation</div>
<div class="relation-path">${escapeHtml(path)}</div>
<div class="relation-meta">${escapeHtml(type)}</div>
`;
}
function filterParentLocations() {
const query = (parentLocationSearch.value || '').trim().toLowerCase();
Array.from(parentLocationSelect.options).forEach((option, index) => {
if (index === 0) {
option.hidden = false;
return;
}
const haystack = `${option.text} ${option.dataset.path || ''} ${option.dataset.type || ''}`.toLowerCase();
option.hidden = query ? !haystack.includes(query) : false;
});
}
if (parentLocationSearch) {
parentLocationSearch.addEventListener('input', filterParentLocations);
}
if (parentLocationSelect) {
parentLocationSelect.addEventListener('change', updateParentSummary);
updateParentSummary();
}
// 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',
has_cross_field: formData.get('has_cross_field') === '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';
}
});
const locationType = document.getElementById('locationType');
const crossFieldOption = document.getElementById('crossFieldOption');
const hasCrossField = document.getElementById('hasCrossField');
const updateCrossFieldOption = () => {
const isRoom = locationType.value === 'rum';
crossFieldOption.hidden = !isRoom;
if (!isRoom) hasCrossField.checked = false;
};
locationType.addEventListener('change', updateCrossFieldOption);
updateCrossFieldOption();
// 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 %}