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

394 lines
18 KiB
HTML
Raw Normal View History

{% extends "shared/frontend/base.html" %}
{% block title %}Wizard: Lokationer - BMC Hub{% endblock %}
{% block content %}
<div class="container-fluid px-4 py-4">
<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">Wizard</li>
</ol>
</nav>
<div class="row mb-4">
<div class="col-12">
<h1 class="h2 fw-700 mb-2">Wizard: Opret lokation</h1>
<p class="text-muted small">Opret en adresse med etager og rum i en samlet arbejdsgang</p>
</div>
</div>
<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 id="wizardForm">
<div class="card border-0 mb-4">
<div class="card-body p-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<h2 class="h5 fw-600 mb-0">Trin 1: Lokation</h2>
<span class="badge bg-primary">Adresse</span>
</div>
<div class="row">
<div class="col-lg-6 mb-3">
<label for="rootName" class="form-label">Navn *</label>
<input type="text" class="form-control" id="rootName" name="root_name" required maxlength="255" placeholder="f.eks. Hovedkontor">
</div>
<div class="col-lg-6 mb-3">
<label for="rootType" class="form-label">Type *</label>
<select class="form-select" id="rootType" name="root_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 %}
{% if option_value not in ['rum', 'kantine', 'moedelokale'] %}
<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 == 'vehicle' %}Køretøj{% else %}{{ option_label }}{% endif %}
</option>
{% endif %}
{% endfor %}
{% endif %}
</select>
<div class="form-text">Tip: Vælg "Bygning" for klassisk etage/rum-setup.</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 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 }}">
{{ parent.name }}{% if parent.location_type %} ({{ parent.location_type }}){% endif %}
</option>
{% endfor %}
{% endif %}
</select>
</div>
<div class="col-lg-6 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 }}">{{ customer.name }}</option>
{% endfor %}
{% endif %}
</select>
</div>
</div>
<div class="row">
<div class="col-lg-6 mb-3">
<label for="addressStreet" class="form-label">Vejnavn og nummer</label>
<input type="text" class="form-control" id="addressStreet" name="address_street" placeholder="f.eks. Hovedgaden 123">
</div>
<div class="col-lg-3 mb-3">
<label for="addressCity" class="form-label">By</label>
<input type="text" class="form-control" id="addressCity" name="address_city" placeholder="f.eks. København">
</div>
<div class="col-lg-3 mb-3">
<label for="addressPostal" class="form-label">Postnummer</label>
<input type="text" class="form-control" id="addressPostal" name="address_postal_code" placeholder="f.eks. 1000">
</div>
</div>
<div class="row">
<div class="col-lg-3 mb-3">
<label for="addressCountry" class="form-label">Land</label>
<input type="text" class="form-control" id="addressCountry" name="address_country" value="DK" placeholder="DK">
</div>
<div class="col-lg-3 mb-3">
<label for="phone" class="form-label">Telefon</label>
<input type="tel" class="form-control" id="phone" name="phone" placeholder="+45 12 34 56 78">
</div>
<div class="col-lg-6 mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="kontakt@lokation.dk">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="rootActive" name="root_active" checked>
<label class="form-check-label" for="rootActive">Lokation er aktiv</label>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="autoPrefix" checked>
<label class="form-check-label" for="autoPrefix">Prefiks etager/rum med lokationsnavn</label>
<div class="form-text">Hjælper mod navnekonflikter (navne skal være unikke globalt).</div>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="autoSuffix" checked>
<label class="form-check-label" for="autoSuffix">Tilføj automatisk suffix ved dubletter</label>
<div class="form-text">Eksempel: "Stue" bliver til "Stue (2)" hvis navnet findes.</div>
</div>
</div>
</div>
<div class="card border-0 mb-4">
<div class="card-body p-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<h2 class="h5 fw-600 mb-0">Trin 2: Etager</h2>
<button type="button" class="btn btn-outline-primary btn-sm" id="addFloorBtn">
<i class="bi bi-plus-lg me-2"></i>Tilføj etage
</button>
</div>
<div id="floorsContainer" class="d-flex flex-column gap-3"></div>
</div>
</div>
<div class="d-flex justify-content-between gap-2">
<a href="{{ cancel_url }}" 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>Opret lokation
</button>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const floorsContainer = document.getElementById('floorsContainer');
const addFloorBtn = document.getElementById('addFloorBtn');
const form = document.getElementById('wizardForm');
const submitBtn = document.getElementById('submitBtn');
const errorAlert = document.getElementById('errorAlert');
const errorMessage = document.getElementById('errorMessage');
let floorIndex = 0;
function createRoomRow(roomIndex) {
const roomRow = document.createElement('div');
roomRow.className = 'row g-2 align-items-center room-row';
roomRow.innerHTML = `
<div class="col-md-6">
<input type="text" class="form-control form-control-sm room-name" placeholder="Rum ${roomIndex + 1}" required>
</div>
<div class="col-md-4">
<select class="form-select form-select-sm room-type">
<option value="rum">Rum</option>
<option value="kantine">Kantine</option>
<option value="moedelokale">Mødelokale</option>
</select>
</div>
<div class="col-md-2 text-end">
<button type="button" class="btn btn-outline-danger btn-sm remove-room">
<i class="bi bi-x-lg"></i>
</button>
</div>
`;
return roomRow;
}
function addRoom(floorCard) {
const roomsContainer = floorCard.querySelector('.rooms-container');
const roomIndex = roomsContainer.querySelectorAll('.room-row').length;
const roomRow = createRoomRow(roomIndex);
roomsContainer.appendChild(roomRow);
roomRow.querySelector('.remove-room').addEventListener('click', function() {
roomRow.remove();
});
}
function addFloor() {
const floorCard = document.createElement('div');
floorCard.className = 'border rounded-3 p-3 bg-light';
floorCard.dataset.floorIndex = String(floorIndex);
floorCard.innerHTML = `
<div class="d-flex justify-content-between align-items-center mb-2">
<div class="fw-600">Etage</div>
<button type="button" class="btn btn-outline-danger btn-sm remove-floor">
<i class="bi bi-trash"></i>
</button>
</div>
<div class="row g-2 align-items-center mb-3">
<div class="col-md-8">
<input type="text" class="form-control floor-name" placeholder="Etage ${floorIndex + 1}" required>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input floor-active" type="checkbox" checked>
<label class="form-check-label">Aktiv</label>
</div>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mb-2">
<div class="text-muted small">Rum</div>
<button type="button" class="btn btn-outline-primary btn-sm add-room">
<i class="bi bi-plus-lg me-2"></i>Tilføj rum
</button>
</div>
<div class="rooms-container d-flex flex-column gap-2"></div>
`;
floorCard.querySelector('.remove-floor').addEventListener('click', function() {
floorCard.remove();
});
floorCard.querySelector('.add-room').addEventListener('click', function() {
addRoom(floorCard);
});
floorsContainer.appendChild(floorCard);
addRoom(floorCard);
floorIndex += 1;
}
addFloorBtn.addEventListener('click', addFloor);
addFloor();
form.addEventListener('submit', async function(event) {
event.preventDefault();
errorAlert.classList.add('hide');
const rootName = document.getElementById('rootName').value.trim();
const rootType = document.getElementById('rootType').value;
const autoPrefix = document.getElementById('autoPrefix').checked;
const autoSuffix = document.getElementById('autoSuffix').checked;
if (!rootName || !rootType) {
errorMessage.textContent = 'Udfyld navn og type for lokationen.';
errorAlert.classList.remove('hide');
return;
}
const floorCards = Array.from(document.querySelectorAll('[data-floor-index]'));
if (floorCards.length === 0) {
errorMessage.textContent = 'Tilføj mindst én etage.';
errorAlert.classList.remove('hide');
return;
}
const floorsPayload = [];
const nameRegistry = new Set();
function registerName(name) {
const normalized = name.trim().toLowerCase();
if (!normalized) {
return false;
}
if (nameRegistry.has(normalized)) {
return false;
}
nameRegistry.add(normalized);
return true;
}
if (!autoSuffix && !registerName(rootName)) {
errorMessage.textContent = 'Der er dublerede navne i lokationen.';
errorAlert.classList.remove('hide');
return;
}
for (const floorCard of floorCards) {
const floorNameInput = floorCard.querySelector('.floor-name').value.trim();
if (!floorNameInput) {
errorMessage.textContent = 'Alle etager skal have et navn.';
errorAlert.classList.remove('hide');
return;
}
const floorName = autoPrefix ? `${rootName} - ${floorNameInput}` : floorNameInput;
if (!autoSuffix && !registerName(floorName)) {
errorMessage.textContent = 'Der er dublerede etagenavne. Skift navne eller brug prefiks.';
errorAlert.classList.remove('hide');
return;
}
const roomsContainer = floorCard.querySelector('.rooms-container');
const roomRows = Array.from(roomsContainer.querySelectorAll('.room-row'));
if (roomRows.length === 0) {
errorMessage.textContent = 'Tilføj mindst ét rum til hver etage.';
errorAlert.classList.remove('hide');
return;
}
const roomsPayload = [];
for (const roomRow of roomRows) {
const roomNameInput = roomRow.querySelector('.room-name').value.trim();
if (!roomNameInput) {
errorMessage.textContent = 'Alle rum skal have et navn.';
errorAlert.classList.remove('hide');
return;
}
const roomName = autoPrefix ? `${rootName} - ${floorNameInput} - ${roomNameInput}` : roomNameInput;
if (!autoSuffix && !registerName(roomName)) {
errorMessage.textContent = 'Der er dublerede rumnavne. Skift navne eller brug prefiks.';
errorAlert.classList.remove('hide');
return;
}
roomsPayload.push({
name: roomName,
location_type: roomRow.querySelector('.room-type').value,
is_active: true
});
}
floorsPayload.push({
name: floorName,
location_type: 'etage',
is_active: floorCard.querySelector('.floor-active').checked,
rooms: roomsPayload
});
}
const payload = {
root: {
name: rootName,
location_type: rootType,
parent_location_id: document.getElementById('parentLocation').value || null,
customer_id: document.getElementById('customerId').value || null,
address_street: document.getElementById('addressStreet').value || null,
address_city: document.getElementById('addressCity').value || null,
address_postal_code: document.getElementById('addressPostal').value || null,
address_country: document.getElementById('addressCountry').value || 'DK',
phone: document.getElementById('phone').value || null,
email: document.getElementById('email').value || null,
notes: null,
is_active: document.getElementById('rootActive').checked
},
floors: floorsPayload,
auto_suffix: autoSuffix
};
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="bi bi-hourglass-split me-2"></i>Opretter...';
try {
const response = await fetch('/api/v1/locations/bulk-create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (response.ok) {
const result = await response.json();
window.location.href = `/app/locations/${result.root_id}`;
return;
}
const errorData = await response.json();
errorMessage.textContent = errorData.detail || 'Fejl ved oprettelse af lokationer.';
errorAlert.classList.remove('hide');
} catch (error) {
console.error('Error:', error);
errorMessage.textContent = 'En fejl opstod. Prøv igen senere.';
errorAlert.classList.remove('hide');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="bi bi-check-lg me-2"></i>Opret lokation';
}
});
});
</script>
{% endblock %}