- Implement tests for the internet connections module, covering routes, IP range creation, and connection validation. - Add tests for the Invoice2DataService to validate extraction from GlobalConnect invoices. - Create tests for subscription network provisioning, ensuring proper handling of network items and IP allocations. - Include validation checks for subtotal mismatches and ensure error handling for missing IP selections.
634 lines
24 KiB
HTML
634 lines
24 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
|
|
|
{% block title %}Internetforbindelser{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.internet-hero {
|
|
background:
|
|
radial-gradient(circle at top right, rgba(15, 76, 117, 0.18), transparent 34%),
|
|
linear-gradient(135deg, rgba(15, 76, 117, 0.1), rgba(255, 255, 255, 0.02));
|
|
border: 1px solid rgba(15, 76, 117, 0.14);
|
|
border-radius: 24px;
|
|
padding: 1.5rem;
|
|
box-shadow: 0 16px 36px rgba(15, 76, 117, 0.08);
|
|
}
|
|
|
|
.internet-panel {
|
|
background: var(--bg-card);
|
|
border: 1px solid rgba(15, 76, 117, 0.12);
|
|
border-radius: 20px;
|
|
box-shadow: 0 14px 32px rgba(15, 76, 117, 0.06);
|
|
}
|
|
|
|
.internet-kpi {
|
|
background: linear-gradient(180deg, rgba(15, 76, 117, 0.04), rgba(15, 76, 117, 0.01));
|
|
border: 1px solid rgba(15, 76, 117, 0.1);
|
|
border-radius: 16px;
|
|
padding: 1rem;
|
|
height: 100%;
|
|
}
|
|
|
|
.internet-kpi-label {
|
|
color: var(--text-secondary);
|
|
font-size: 0.78rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
.internet-kpi-value {
|
|
color: var(--text-primary);
|
|
font-size: 1.55rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.internet-toolbar {
|
|
display: grid;
|
|
grid-template-columns: 1.4fr 1fr 0.8fr 0.9fr auto auto;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.internet-row {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.internet-row td {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.internet-tabs {
|
|
display: inline-flex;
|
|
padding: 0.35rem;
|
|
border-radius: 999px;
|
|
background: rgba(15, 76, 117, 0.07);
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.internet-tab {
|
|
border: 0;
|
|
background: transparent;
|
|
border-radius: 999px;
|
|
padding: 0.6rem 1rem;
|
|
font-weight: 700;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.internet-tab.active {
|
|
background: #0f4c75;
|
|
color: white;
|
|
}
|
|
|
|
.internet-tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
border-radius: 999px;
|
|
padding: 0.18rem 0.5rem;
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
background: rgba(15, 76, 117, 0.08);
|
|
color: #0f4c75;
|
|
margin-right: 0.35rem;
|
|
}
|
|
|
|
.internet-status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0.35rem 0.7rem;
|
|
border-radius: 999px;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.internet-status.active {
|
|
background: rgba(25, 135, 84, 0.12);
|
|
color: #146c43;
|
|
}
|
|
|
|
.internet-status.inactive {
|
|
background: rgba(108, 117, 125, 0.14);
|
|
color: #495057;
|
|
}
|
|
|
|
.internet-status.pending,
|
|
.internet-status.planned {
|
|
background: rgba(255, 193, 7, 0.18);
|
|
color: #997404;
|
|
}
|
|
|
|
.internet-status.terminated,
|
|
.internet-status.cancelled {
|
|
background: rgba(220, 53, 69, 0.12);
|
|
color: #b02a37;
|
|
}
|
|
|
|
.internet-mini {
|
|
color: var(--text-secondary);
|
|
font-size: 0.83rem;
|
|
}
|
|
|
|
.internet-quick-form {
|
|
border: 1px dashed rgba(15, 76, 117, 0.2);
|
|
border-radius: 18px;
|
|
background: rgba(15, 76, 117, 0.03);
|
|
padding: 1rem;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
.internet-toolbar {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-4">
|
|
<div class="internet-hero mb-4">
|
|
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
|
<div>
|
|
<div class="small text-uppercase fw-semibold text-muted mb-2">Økonomi / Drift / Support</div>
|
|
<h2 class="h3 mb-1">Internetforbindelser</h2>
|
|
<div class="text-muted">Samlet overblik over forbindelser, kunder, IP-adresser, kontrakter og dækningsbidrag.</div>
|
|
</div>
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
<button class="btn btn-outline-secondary" type="button" onclick="loadInternetPage()">
|
|
<i class="bi bi-arrow-repeat me-1"></i>Opdater
|
|
</button>
|
|
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#createConnectionBlock">
|
|
<i class="bi bi-plus-lg me-1"></i>Ny forbindelse
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="internet-tabs mt-3">
|
|
<button class="internet-tab active" type="button" id="tabAll" onclick="setActiveTab('all')">Alle forbindelser</button>
|
|
<button class="internet-tab" type="button" id="tabShared" onclick="setActiveTab('shared')">Delte hovedforbindelser</button>
|
|
<button class="internet-tab" type="button" id="tabBmcnet" onclick="setActiveTab('bmcnet')">BMCnet</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-6 col-xl-3">
|
|
<div class="internet-kpi">
|
|
<div class="internet-kpi-label">Forbindelser</div>
|
|
<div class="internet-kpi-value" id="metricTotal">0</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-xl-3">
|
|
<div class="internet-kpi">
|
|
<div class="internet-kpi-label">Aktive</div>
|
|
<div class="internet-kpi-value" id="metricActive">0</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-xl-3">
|
|
<div class="internet-kpi">
|
|
<div class="internet-kpi-label" id="metricSharedLabel">Delte hoveder</div>
|
|
<div class="internet-kpi-value" id="metricShared">0</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-xl-3">
|
|
<div class="internet-kpi">
|
|
<div class="internet-kpi-label">Dækningsbidrag</div>
|
|
<div class="internet-kpi-value" id="metricMargin">0 kr.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="internet-panel p-4 mb-4 collapse" id="createConnectionBlock">
|
|
<div class="internet-quick-form">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<div class="fw-semibold">Opret ny forbindelse</div>
|
|
<div class="internet-mini">Bruges til fysiske og interne BMC-forbindelser.</div>
|
|
</div>
|
|
<div class="small text-muted" id="createConnectionFeedback" role="status"></div>
|
|
</div>
|
|
<div class="row g-2">
|
|
<div class="col-lg-3">
|
|
<input type="text" class="form-control" id="connectionNameInput" placeholder="Navn" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="text" class="form-control" id="connectionProviderInput" placeholder="Leverandør" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="number" class="form-control" id="connectionCustomerIdInput" placeholder="Kunde-ID" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="text" class="form-control" id="connectionCircuitInput" placeholder="Kredsløb" />
|
|
</div>
|
|
<div class="col-lg-3">
|
|
<input type="text" class="form-control" id="connectionAddressInput" placeholder="Installationsadresse" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<select class="form-select" id="connectionAllocationInput">
|
|
<option value="dedicated">Dedikeret</option>
|
|
<option value="shared">Delt</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<select class="form-select" id="connectionValueTypeInput" onchange="toggleCreateValueFields()">
|
|
<option value="other">Anden værdi</option>
|
|
<option value="subscription">Abonnement</option>
|
|
<option value="bmc_networks">BMC Networks</option>
|
|
<option value="delefiber">Delefiber</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-lg-4" id="createValueLabelWrap">
|
|
<input type="text" class="form-control" id="connectionValueLabelInput" placeholder="Værdi / klassifikation" value="Mangler klassifikation" />
|
|
</div>
|
|
<div class="col-lg-4 d-none" id="createSubscriptionWrap">
|
|
<input type="text" class="form-control" id="connectionSubscriptionInput" list="subscriptionLookupList" placeholder="Abonnement">
|
|
<input type="hidden" id="connectionSubscriptionIdInput" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="text" class="form-control" id="connectionTechnologyInput" placeholder="Teknologi" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="number" class="form-control" id="connectionDownloadInput" placeholder="Download" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="number" class="form-control" id="connectionUploadInput" placeholder="Upload" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="number" class="form-control" id="connectionPurchaseInput" placeholder="Kost" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<input type="number" class="form-control" id="connectionSalesInput" placeholder="Salg" />
|
|
</div>
|
|
<div class="col-lg-2">
|
|
<select class="form-select" id="connectionStatusInput">
|
|
<option value="active">Aktiv</option>
|
|
<option value="planned">Planlagt</option>
|
|
<option value="inactive">Inaktiv</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<textarea class="form-control" id="connectionNotesInput" rows="2" placeholder="Noter, SLA, overvågning, intern struktur..."></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<button class="btn btn-primary" type="button" onclick="submitConnectionForm()">Gem forbindelse</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="internet-panel p-4 mb-4">
|
|
<div class="internet-toolbar mb-3">
|
|
<input type="search" class="form-control" id="searchInput" placeholder="Søg navn, kunde, leverandør, kredsløb eller adresse" />
|
|
<input type="text" class="form-control" id="providerFilter" placeholder="Filtrer leverandør" />
|
|
<select class="form-select" id="statusFilter">
|
|
<option value="">Alle statusser</option>
|
|
<option value="active">Aktive</option>
|
|
<option value="planned">Planlagte</option>
|
|
<option value="inactive">Inaktive</option>
|
|
<option value="terminated">Opsagte</option>
|
|
</select>
|
|
<select class="form-select" id="valueTypeFilter">
|
|
<option value="">Alle værdier</option>
|
|
<option value="subscription">Abonnement</option>
|
|
<option value="bmc_networks">BMC Networks</option>
|
|
<option value="delefiber">Delefiber</option>
|
|
<option value="other">Anden</option>
|
|
</select>
|
|
<button class="btn btn-outline-secondary" type="button" onclick="loadInternetPage()">Anvend</button>
|
|
<button class="btn btn-light border" type="button" onclick="resetFilters()">Nulstil</button>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-xl-6">
|
|
<div class="internet-mini" id="pageSummaryText">Indlæser forbindelser...</div>
|
|
</div>
|
|
<div class="col-xl-6 text-xl-end">
|
|
<div class="internet-mini">Klik på en række for at åbne forbindelsesdetaljer.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table align-middle mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Forbindelse</th>
|
|
<th>Kunde</th>
|
|
<th>Leverandør / kredsløb</th>
|
|
<th>Hastighed / IP</th>
|
|
<th>Økonomi</th>
|
|
<th>Status</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="connectionsTableBody">
|
|
<tr>
|
|
<td colspan="7" class="text-muted py-4">Indlæser...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let allConnections = [];
|
|
let activeTab = 'all';
|
|
let subscriptionOptions = [];
|
|
|
|
function formatDKK(value) {
|
|
return Number(value || 0).toLocaleString('da-DK', { style: 'currency', currency: 'DKK', minimumFractionDigits: 0 });
|
|
}
|
|
|
|
function formatSpeed(item) {
|
|
const down = Number(item.download_mbps || 0);
|
|
const up = Number(item.upload_mbps || 0);
|
|
const speed = Number(item.speed_mbps || 0);
|
|
if (down || up) return `${down || 0}/${up || 0} Mbps`;
|
|
if (speed) return `${speed} Mbps`;
|
|
return '-';
|
|
}
|
|
|
|
function statusBadge(status) {
|
|
const value = String(status || 'inactive').toLowerCase();
|
|
const labelMap = {
|
|
active: 'Aktiv',
|
|
planned: 'Planlagt',
|
|
pending: 'Afventer',
|
|
inactive: 'Inaktiv',
|
|
terminated: 'Opsagt',
|
|
cancelled: 'Annulleret',
|
|
};
|
|
return `<span class="internet-status ${value}">${labelMap[value] || value}</span>`;
|
|
}
|
|
|
|
function allocationBadge(item) {
|
|
const label = item.allocation_model_label || (item.allocation_model === 'shared' ? 'Delt' : 'Dedikeret');
|
|
return `<span class="internet-tag">${label}</span>`;
|
|
}
|
|
|
|
function valueBadge(item) {
|
|
const label = item.value_type_label || 'Anden';
|
|
return `<span class="internet-tag">${label}</span>`;
|
|
}
|
|
|
|
function currentTabDescription() {
|
|
if (activeTab === 'shared') return ' i delte hovedforbindelser';
|
|
if (activeTab === 'bmcnet') return ' i BMCnet';
|
|
return '';
|
|
}
|
|
|
|
async function safeJson(response, fallback) {
|
|
if (!response.ok) return fallback;
|
|
try {
|
|
return await response.json();
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
async function extractErrorMessage(response, fallback) {
|
|
try {
|
|
const payload = await response.clone().json();
|
|
if (typeof payload?.detail === 'string' && payload.detail.trim()) return payload.detail.trim();
|
|
if (Array.isArray(payload?.detail)) {
|
|
const validation = payload.detail
|
|
.map((item) => item?.msg || item?.message || '')
|
|
.filter(Boolean)
|
|
.join(', ');
|
|
if (validation) return validation;
|
|
}
|
|
} catch (_) {}
|
|
|
|
try {
|
|
const text = (await response.text()).trim();
|
|
if (text) return text;
|
|
} catch (_) {}
|
|
return fallback;
|
|
}
|
|
|
|
async function loadInternetPage() {
|
|
const search = document.getElementById('searchInput').value.trim();
|
|
const provider = document.getElementById('providerFilter').value.trim();
|
|
const status = document.getElementById('statusFilter').value;
|
|
const valueType = document.getElementById('valueTypeFilter').value;
|
|
const params = new URLSearchParams();
|
|
if (search) params.set('q', search);
|
|
if (provider) params.set('provider', provider);
|
|
if (status) params.set('status', status);
|
|
if (valueType) params.set('value_type', valueType);
|
|
if (activeTab === 'shared') params.set('shared_only', 'true');
|
|
if (activeTab === 'bmcnet') params.set('bmcnet_only', 'true');
|
|
|
|
try {
|
|
const connectionsResponse = await fetch(`/api/v1/internet-connections?${params.toString()}`);
|
|
|
|
const connections = await safeJson(connectionsResponse, []);
|
|
allConnections = Array.isArray(connections) ? connections : [];
|
|
const total = allConnections.length;
|
|
const active = allConnections.filter((item) => item.status === 'active').length;
|
|
const sharedHeads = allConnections.filter((item) => item.is_shared_head).length;
|
|
const bmcnetConnections = allConnections.filter((item) => item.is_bmcnet_connection).length;
|
|
const margin = allConnections.reduce((sum, item) => sum + Number(item.margin_amount || 0), 0);
|
|
|
|
document.getElementById('metricTotal').textContent = String(total);
|
|
document.getElementById('metricActive').textContent = String(active);
|
|
document.getElementById('metricSharedLabel').textContent = activeTab === 'bmcnet' ? 'BMCnet' : 'Delte hoveder';
|
|
document.getElementById('metricShared').textContent = String(activeTab === 'bmcnet' ? bmcnetConnections : sharedHeads);
|
|
document.getElementById('metricMargin').textContent = formatDKK(margin);
|
|
|
|
renderConnections(allConnections);
|
|
} catch (error) {
|
|
document.getElementById('connectionsTableBody').innerHTML = '<tr><td colspan="7" class="text-danger py-4">Kunne ikke indlæse internetforbindelser.</td></tr>';
|
|
document.getElementById('pageSummaryText').textContent = 'Indlæsning fejlede.';
|
|
}
|
|
}
|
|
|
|
function renderConnections(connections) {
|
|
const body = document.getElementById('connectionsTableBody');
|
|
document.getElementById('pageSummaryText').textContent = `${connections.length} forbindelser vist${currentTabDescription()}`;
|
|
|
|
if (!connections.length) {
|
|
body.innerHTML = '<tr><td colspan="7" class="text-muted py-4">Ingen forbindelser matcher filtrene.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
body.innerHTML = connections.map((item) => `
|
|
<tr class="internet-row" onclick="window.location.href='/economy/internet-connections/${item.id}'">
|
|
<td>
|
|
<div class="fw-semibold">${item.name || '-'}</div>
|
|
<div class="mb-1">${allocationBadge(item)}${valueBadge(item)}</div>
|
|
<div class="internet-mini">${item.address || '-'}</div>
|
|
${item.parent_name ? `<div class="internet-mini"><i class="bi bi-diagram-2 me-1"></i>Under ${item.parent_name}</div>` : ''}
|
|
${item.is_shared_head ? `<div class="internet-mini"><i class="bi bi-diagram-3 me-1"></i>${Number(item.bmcnet_child_count || 0)} BMCnet-kunder · ${formatDKK(item.bmcnet_child_sales_price || 0)} salg</div>` : ''}
|
|
</td>
|
|
<td>
|
|
<div>${item.customer_name || '-'}</div>
|
|
<div class="internet-mini">Kunde-ID: ${item.customer_id || '-'}</div>
|
|
</td>
|
|
<td>
|
|
<div>${item.provider || '-'}</div>
|
|
<div class="internet-mini">${item.circuit_number || 'Intet kredsløb'}</div>
|
|
${item.subscription_number ? `<div class="internet-mini">Abonnement ${item.subscription_number} · ${item.subscription_product_name || '-'}</div>` : (item.value_label ? `<div class="internet-mini">${item.value_label}</div>` : '')}
|
|
</td>
|
|
<td>
|
|
<div>${formatSpeed(item)}</div>
|
|
<div class="internet-mini">Ranges ${Number(item.ip_range_count || 0)} · IP i brug: ${Number(item.in_use_ip_addresses || 0)} / ${Number(item.total_ip_addresses || 0)}</div>
|
|
${item.is_shared_head ? `<div class="internet-mini">BMCnet-IP i brug: ${Number(item.bmcnet_child_ip_count || 0)}</div>` : ''}
|
|
</td>
|
|
<td>
|
|
<div>${formatDKK(item.sales_price || 0)}</div>
|
|
<div class="internet-mini">Kost ${formatDKK(item.monthly_cost || 0)} · DB ${formatDKK(item.margin_amount || 0)}</div>
|
|
</td>
|
|
<td>${statusBadge(item.status)}</td>
|
|
<td class="text-end">
|
|
<a href="/economy/internet-connections/${item.id}" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
|
|
Åbn
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
|
|
async function submitConnectionForm() {
|
|
const feedback = document.getElementById('createConnectionFeedback');
|
|
const saveButton = document.querySelector('#createConnectionBlock button.btn.btn-primary');
|
|
const payload = {
|
|
name: document.getElementById('connectionNameInput').value.trim(),
|
|
provider: document.getElementById('connectionProviderInput').value.trim() || null,
|
|
customer_id: Number(document.getElementById('connectionCustomerIdInput').value || 0) || null,
|
|
circuit_number: document.getElementById('connectionCircuitInput').value.trim() || null,
|
|
address: document.getElementById('connectionAddressInput').value.trim() || null,
|
|
technology: document.getElementById('connectionTechnologyInput').value.trim() || null,
|
|
download_mbps: Number(document.getElementById('connectionDownloadInput').value || 0) || null,
|
|
upload_mbps: Number(document.getElementById('connectionUploadInput').value || 0) || null,
|
|
monthly_cost: Number(document.getElementById('connectionPurchaseInput').value || 0),
|
|
sales_price: Number(document.getElementById('connectionSalesInput').value || 0),
|
|
status: document.getElementById('connectionStatusInput').value || 'active',
|
|
notes: document.getElementById('connectionNotesInput').value.trim() || null,
|
|
connection_type: 'fiber',
|
|
allocation_model: document.getElementById('connectionAllocationInput').value || 'dedicated',
|
|
value_type: document.getElementById('connectionValueTypeInput').value || 'other',
|
|
value_label: document.getElementById('connectionValueLabelInput').value.trim() || null,
|
|
subscription_id: Number(document.getElementById('connectionSubscriptionIdInput').value || 0) || null,
|
|
};
|
|
|
|
if (!payload.name) {
|
|
feedback.textContent = 'Navn er påkrævet.';
|
|
return;
|
|
}
|
|
if (!payload.address) {
|
|
feedback.textContent = 'Adresse er påkrævet.';
|
|
return;
|
|
}
|
|
if (payload.value_type === 'subscription' && !payload.subscription_id) {
|
|
feedback.textContent = 'Vælg et gyldigt abonnement.';
|
|
return;
|
|
}
|
|
|
|
feedback.textContent = 'Gemmer...';
|
|
if (saveButton) saveButton.disabled = true;
|
|
try {
|
|
const response = await fetch('/api/v1/internet-connections', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
feedback.textContent = await extractErrorMessage(response, 'Kunne ikke gemme forbindelsen.');
|
|
return;
|
|
}
|
|
|
|
feedback.textContent = 'Forbindelse oprettet.';
|
|
[
|
|
'connectionNameInput',
|
|
'connectionProviderInput',
|
|
'connectionCustomerIdInput',
|
|
'connectionCircuitInput',
|
|
'connectionAddressInput',
|
|
'connectionTechnologyInput',
|
|
'connectionDownloadInput',
|
|
'connectionUploadInput',
|
|
'connectionPurchaseInput',
|
|
'connectionSalesInput',
|
|
'connectionNotesInput',
|
|
].forEach((id) => {
|
|
const field = document.getElementById(id);
|
|
if (field) field.value = '';
|
|
});
|
|
document.getElementById('connectionStatusInput').value = 'active';
|
|
document.getElementById('connectionAllocationInput').value = 'dedicated';
|
|
document.getElementById('connectionValueTypeInput').value = 'other';
|
|
document.getElementById('connectionValueLabelInput').value = 'Mangler klassifikation';
|
|
document.getElementById('connectionSubscriptionInput').value = '';
|
|
document.getElementById('connectionSubscriptionIdInput').value = '';
|
|
toggleCreateValueFields();
|
|
await loadInternetPage();
|
|
} catch (error) {
|
|
feedback.textContent = error?.message || 'Netværksfejl under gem.';
|
|
} finally {
|
|
if (saveButton) saveButton.disabled = false;
|
|
}
|
|
}
|
|
|
|
function resetFilters() {
|
|
document.getElementById('searchInput').value = '';
|
|
document.getElementById('providerFilter').value = '';
|
|
document.getElementById('statusFilter').value = '';
|
|
document.getElementById('valueTypeFilter').value = '';
|
|
loadInternetPage();
|
|
}
|
|
|
|
function setActiveTab(tab) {
|
|
activeTab = tab;
|
|
document.getElementById('tabAll').classList.toggle('active', tab === 'all');
|
|
document.getElementById('tabShared').classList.toggle('active', tab === 'shared');
|
|
document.getElementById('tabBmcnet').classList.toggle('active', tab === 'bmcnet');
|
|
loadInternetPage();
|
|
}
|
|
|
|
function toggleCreateValueFields() {
|
|
const valueType = document.getElementById('connectionValueTypeInput').value;
|
|
document.getElementById('createValueLabelWrap').classList.toggle('d-none', valueType !== 'other');
|
|
document.getElementById('createSubscriptionWrap').classList.toggle('d-none', valueType !== 'subscription');
|
|
}
|
|
|
|
function buildSubscriptionLabel(item) {
|
|
return `${item.id} · ${item.subscription_number || '-'} · ${item.product_name || '-'} · ${item.customer_name || '-'}`;
|
|
}
|
|
|
|
function resolveSubscriptionId(value) {
|
|
const raw = String(value || '').trim();
|
|
if (!raw) return null;
|
|
const idMatch = raw.match(/^(\d+)\b/);
|
|
if (idMatch) return Number(idMatch[1]);
|
|
const matched = subscriptionOptions.find((item) => buildSubscriptionLabel(item).toLowerCase() === raw.toLowerCase());
|
|
return matched ? Number(matched.id) : null;
|
|
}
|
|
|
|
async function loadSubscriptionOptions() {
|
|
try {
|
|
const response = await fetch('/api/v1/internet-connections/subscription-options');
|
|
subscriptionOptions = response.ok ? await response.json() : [];
|
|
} catch (error) {
|
|
subscriptionOptions = [];
|
|
}
|
|
document.getElementById('subscriptionLookupList').innerHTML = subscriptionOptions
|
|
.map((item) => `<option value="${buildSubscriptionLabel(item)}"></option>`)
|
|
.join('');
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
document.getElementById('searchInput').addEventListener('keydown', (event) => {
|
|
if (event.key === 'Enter') loadInternetPage();
|
|
});
|
|
document.getElementById('connectionSubscriptionInput').addEventListener('change', () => {
|
|
document.getElementById('connectionSubscriptionIdInput').value = resolveSubscriptionId(document.getElementById('connectionSubscriptionInput').value) || '';
|
|
});
|
|
toggleCreateValueFields();
|
|
await loadSubscriptionOptions();
|
|
await loadInternetPage();
|
|
});
|
|
</script>
|
|
<datalist id="subscriptionLookupList"></datalist>
|
|
{% endblock %}
|