- 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.
558 lines
20 KiB
HTML
558 lines
20 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
|
|
|
{% block title %}Internet Wizard v2{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.wiz-hero {
|
|
background:
|
|
radial-gradient(circle at top right, rgba(15, 76, 117, 0.16), transparent 36%),
|
|
linear-gradient(135deg, rgba(15, 76, 117, 0.1), rgba(255, 255, 255, 0.02));
|
|
border: 1px solid rgba(15, 76, 117, 0.12);
|
|
border-radius: 24px;
|
|
padding: 1.5rem;
|
|
box-shadow: 0 14px 34px rgba(15, 76, 117, 0.06);
|
|
}
|
|
|
|
.wiz-panel {
|
|
background: var(--bg-card);
|
|
border: 1px solid rgba(15, 76, 117, 0.12);
|
|
border-radius: 20px;
|
|
box-shadow: 0 14px 30px rgba(15, 76, 117, 0.05);
|
|
}
|
|
|
|
.wiz-kpi {
|
|
border: 1px solid rgba(15, 76, 117, 0.1);
|
|
border-radius: 16px;
|
|
padding: 1rem;
|
|
background: linear-gradient(180deg, rgba(15, 76, 117, 0.04), rgba(15, 76, 117, 0.01));
|
|
}
|
|
|
|
.wiz-kpi-label {
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--text-secondary);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.wiz-kpi-value {
|
|
font-size: 1.4rem;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.wiz-grid {
|
|
display: grid;
|
|
grid-template-columns: 1.2fr 0.8fr;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.wiz-card {
|
|
border: 1px solid rgba(15, 76, 117, 0.1);
|
|
border-radius: 16px;
|
|
padding: 1rem;
|
|
background: rgba(15, 76, 117, 0.03);
|
|
}
|
|
|
|
.wiz-card h3 {
|
|
font-size: 1rem;
|
|
margin-bottom: 0.35rem;
|
|
}
|
|
|
|
.wiz-meta {
|
|
color: var(--text-secondary);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.wiz-snippet {
|
|
border-left: 3px solid rgba(15, 76, 117, 0.25);
|
|
padding-left: 0.8rem;
|
|
margin-top: 0.75rem;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.wiz-upload-box {
|
|
border: 1px dashed rgba(15, 76, 117, 0.24);
|
|
border-radius: 16px;
|
|
padding: 1rem;
|
|
background: rgba(15, 76, 117, 0.025);
|
|
}
|
|
|
|
.wiz-empty {
|
|
color: var(--text-secondary);
|
|
font-style: italic;
|
|
}
|
|
|
|
.wiz-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
border-radius: 999px;
|
|
padding: 0.35rem 0.7rem;
|
|
background: rgba(15, 76, 117, 0.08);
|
|
color: #0f4c75;
|
|
font-size: 0.78rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.wiz-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.85rem;
|
|
}
|
|
|
|
.wiz-summary {
|
|
min-height: 110px;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
.wiz-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-4">
|
|
<div class="wiz-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">Data migration / Internet</div>
|
|
<h2 class="h3 mb-1">Internet Wizard v2</h2>
|
|
<div class="text-muted">Midlertidig research-wizard til gamle kundetekster, internetnoter og leverandørfakturaer fra de sidste 18 måneder.</div>
|
|
</div>
|
|
<div class="wiz-pill">
|
|
<i class="bi bi-magic"></i>
|
|
Temp version
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wiz-panel p-4 mb-4">
|
|
<div class="row g-3 align-items-end">
|
|
<div class="col-lg-5">
|
|
<label class="form-label fw-semibold">Kunde</label>
|
|
<input type="text" class="form-control" id="customerSearchInput" list="customerOptions" placeholder="Søg kunde og vælg fra listen">
|
|
<datalist id="customerOptions"></datalist>
|
|
<input type="hidden" id="selectedCustomerId">
|
|
<div class="wiz-meta mt-2" id="selectedCustomerMeta">Ingen kunde valgt endnu.</div>
|
|
</div>
|
|
<div class="col-lg-5">
|
|
<label class="form-label fw-semibold">Søgning / fokus</label>
|
|
<input type="text" class="form-control" id="queryInput" placeholder="fx fiber, MPLS, Lejrvej, public IP, gammel aftale">
|
|
</div>
|
|
<div class="col-lg-2 d-grid">
|
|
<button class="btn btn-primary" type="button" onclick="loadWizardContext()">
|
|
<i class="bi bi-search me-1"></i>Vis info
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-6 col-xl-3">
|
|
<div class="wiz-kpi">
|
|
<div class="wiz-kpi-label">Kundefiler</div>
|
|
<div class="wiz-kpi-value" id="metricDocuments">0</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-xl-3">
|
|
<div class="wiz-kpi">
|
|
<div class="wiz-kpi-label">Blokfund</div>
|
|
<div class="wiz-kpi-value" id="metricSnippets">0</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-xl-3">
|
|
<div class="wiz-kpi">
|
|
<div class="wiz-kpi-label">Internetfakturaer</div>
|
|
<div class="wiz-kpi-value" id="metricInvoices">0</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 col-xl-3">
|
|
<div class="wiz-kpi">
|
|
<div class="wiz-kpi-label">Status</div>
|
|
<div class="wiz-kpi-value fs-6" id="wizardStatusText">Venter</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wiz-grid mb-4">
|
|
<div class="wiz-panel p-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h3 class="mb-1">AI-overblik</h3>
|
|
<div class="wiz-meta">Kort opsummering fra kundefiler og relevante internetfakturaer.</div>
|
|
</div>
|
|
</div>
|
|
<div class="wiz-card wiz-summary" id="aiSummaryBox">Vælg en kunde for at hente kontekst.</div>
|
|
</div>
|
|
|
|
<div class="wiz-panel p-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h3 class="mb-1">Upload tekstfiler</h3>
|
|
<div class="wiz-meta">Upload delte batch-filer med flere kunder. Systemet splitter dem i søgbare blokke med fx IP, CIDR, stiknr og referencer.</div>
|
|
</div>
|
|
</div>
|
|
<div class="wiz-upload-box">
|
|
<div class="mb-2">
|
|
<input type="file" class="form-control" id="customerFileInput" accept=".txt,.csv,.log,.md">
|
|
</div>
|
|
<div class="mb-2">
|
|
<textarea class="form-control" id="customerFileNotes" rows="3" placeholder="Noter om filen eller kontekst"></textarea>
|
|
</div>
|
|
<div class="d-grid">
|
|
<button class="btn btn-outline-primary" type="button" onclick="uploadCustomerFile()">
|
|
<i class="bi bi-upload me-1"></i>Upload fil
|
|
</button>
|
|
</div>
|
|
<div class="wiz-meta mt-2" id="uploadStatus">Ingen upload kørt endnu.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-xl-6">
|
|
<div class="wiz-panel p-4 h-100">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h3 class="mb-1">Kundetekster</h3>
|
|
<div class="wiz-meta">Uploadede filer og de bedste fund fra dem.</div>
|
|
</div>
|
|
</div>
|
|
<div class="wiz-list" id="documentList">
|
|
<div class="wiz-empty">Ingen dokumenter endnu.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-6">
|
|
<div class="wiz-panel p-4 mb-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h3 class="mb-1">Præcise blokfund</h3>
|
|
<div class="wiz-meta">Her finder du den konkrete tekstblok med fx IP, stiknr eller reference.</div>
|
|
</div>
|
|
</div>
|
|
<div class="wiz-list" id="segmentList">
|
|
<div class="wiz-empty">Ingen blokfund endnu.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-12">
|
|
<div class="wiz-panel p-4 h-100">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h3 class="mb-1">Relaterede fakturaer</h3>
|
|
<div class="wiz-meta">De sidste 18 måneder med internet/bredbånd-relateret tekst.</div>
|
|
</div>
|
|
</div>
|
|
<div class="wiz-list" id="invoiceList">
|
|
<div class="wiz-empty">Ingen fakturafund endnu.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
let customerSearchTimer = null;
|
|
let customerOptions = [];
|
|
let localUploadedDocuments = [];
|
|
|
|
function escapeHtml(value) {
|
|
return String(value ?? '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function formatDate(value) {
|
|
if (!value) return '-';
|
|
const parsed = new Date(value);
|
|
if (Number.isNaN(parsed.getTime())) return value;
|
|
return parsed.toLocaleDateString('da-DK');
|
|
}
|
|
|
|
function formatBytes(value) {
|
|
const size = Number(value || 0);
|
|
if (!size) return '0 B';
|
|
if (size < 1024) return `${size} B`;
|
|
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
|
|
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
function formatMoney(value, currency = 'DKK') {
|
|
return new Intl.NumberFormat('da-DK', {
|
|
style: 'currency',
|
|
currency: currency || 'DKK',
|
|
minimumFractionDigits: 2,
|
|
}).format(Number(value || 0));
|
|
}
|
|
|
|
function setWizardStatus(text) {
|
|
document.getElementById('wizardStatusText').textContent = text;
|
|
}
|
|
|
|
function renderLocalDocumentsOnly(documents = []) {
|
|
document.getElementById('metricDocuments').textContent = String(documents.length);
|
|
document.getElementById('metricSnippets').textContent = '0';
|
|
document.getElementById('metricInvoices').textContent = '0';
|
|
document.getElementById('aiSummaryBox').textContent = 'Viser uploadede filer. Vælg kunde eller genindlæs siden efter backend-genstart for fuld kontekst.';
|
|
|
|
const documentList = document.getElementById('documentList');
|
|
if (!documents.length) {
|
|
documentList.innerHTML = '<div class="wiz-empty">Ingen dokumenter i det delte arkiv endnu.</div>';
|
|
} else {
|
|
documentList.innerHTML = documents.map(item => `
|
|
<div class="wiz-card">
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<h3>${escapeHtml(item.original_filename || item.filename || 'Uploadet fil')}</h3>
|
|
<div class="wiz-meta">${item.created_at ? formatDate(item.created_at) + ' · ' : ''}${item.file_size ? formatBytes(item.file_size) + ' · ' : ''}${escapeHtml(item.mime_type || '-')}</div>
|
|
</div>
|
|
<span class="wiz-pill">${item.segment_count || 0} blokke</span>
|
|
</div>
|
|
${(item.notes ? `<div class="wiz-meta mt-2">${escapeHtml(item.notes)}</div>` : '')}
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
document.getElementById('segmentList').innerHTML = '<div class="wiz-empty">Vælg kunde eller søg på IP/stiknr/reference for konkrete blokfund.</div>';
|
|
document.getElementById('invoiceList').innerHTML = '<div class="wiz-empty">Vælg kunde for at se relaterede internetfakturaer.</div>';
|
|
}
|
|
|
|
function renderCustomerOptions(items) {
|
|
customerOptions = items || [];
|
|
const list = document.getElementById('customerOptions');
|
|
list.innerHTML = customerOptions.map(item => (
|
|
`<option value="${escapeHtml(item.name)}" data-id="${item.id}"></option>`
|
|
)).join('');
|
|
}
|
|
|
|
async function searchCustomers(query) {
|
|
const response = await fetch(`/api/v1/customers?search=${encodeURIComponent(query)}&limit=15&is_active=true`);
|
|
if (!response.ok) return [];
|
|
const payload = await response.json();
|
|
return Array.isArray(payload) ? payload : (payload.customers || payload.items || []);
|
|
}
|
|
|
|
async function onCustomerInputChanged() {
|
|
const input = document.getElementById('customerSearchInput');
|
|
const query = input.value.trim();
|
|
document.getElementById('selectedCustomerId').value = '';
|
|
document.getElementById('selectedCustomerMeta').textContent = 'Vælg kunde fra listen.';
|
|
|
|
const exact = customerOptions.find(item => item.name === query);
|
|
if (exact) {
|
|
document.getElementById('selectedCustomerId').value = exact.id;
|
|
document.getElementById('selectedCustomerMeta').textContent = `Kunde-ID ${exact.id} · ${exact.name}`;
|
|
return;
|
|
}
|
|
|
|
if (query.length < 2) return;
|
|
const items = await searchCustomers(query);
|
|
renderCustomerOptions(items);
|
|
}
|
|
|
|
async function loadWizardContext() {
|
|
const customerId = document.getElementById('selectedCustomerId').value;
|
|
const query = document.getElementById('queryInput').value.trim();
|
|
setWizardStatus('Henter');
|
|
if (!customerId && !query) {
|
|
try {
|
|
const docRes = await fetch('/api/v1/internet-connections/customer-documents');
|
|
if (docRes.ok) {
|
|
const docPayload = await docRes.json();
|
|
const docs = docPayload.documents || [];
|
|
renderLocalDocumentsOnly(docs);
|
|
setWizardStatus('Klar');
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
console.warn('Could not load shared document list', error);
|
|
}
|
|
if (localUploadedDocuments.length) {
|
|
renderLocalDocumentsOnly(localUploadedDocuments);
|
|
setWizardStatus('Klar');
|
|
return;
|
|
}
|
|
}
|
|
const params = new URLSearchParams();
|
|
if (customerId) {
|
|
params.set('customer_id', customerId);
|
|
}
|
|
if (query) {
|
|
params.set('query', query);
|
|
}
|
|
const response = await fetch(`/api/v1/internet-connections/migration-wizard-v2/context?${params.toString()}`);
|
|
if (!response.ok) {
|
|
let payload = {};
|
|
try {
|
|
payload = await response.json();
|
|
} catch (error) {
|
|
payload = {};
|
|
}
|
|
const detail = JSON.stringify(payload.detail || '');
|
|
if (!customerId && detail.includes('customer_id')) {
|
|
if (localUploadedDocuments.length) {
|
|
renderLocalDocumentsOnly(localUploadedDocuments);
|
|
setWizardStatus('Klar');
|
|
return;
|
|
}
|
|
setWizardStatus('Venter');
|
|
return;
|
|
}
|
|
setWizardStatus('Fejl');
|
|
const errorText = payload.detail ? JSON.stringify(payload.detail) : await response.text();
|
|
alert(`Kunne ikke hente kontekst: ${errorText}`);
|
|
return;
|
|
}
|
|
const payload = await response.json();
|
|
renderWizardContext(payload);
|
|
setWizardStatus('Klar');
|
|
}
|
|
|
|
function renderWizardContext(payload) {
|
|
const documents = payload.documents || [];
|
|
const invoiceHits = payload.invoice_hits || [];
|
|
const segmentHits = payload.segment_hits || [];
|
|
const customer = payload.customer || null;
|
|
|
|
document.getElementById('metricDocuments').textContent = String(documents.length);
|
|
document.getElementById('metricSnippets').textContent = String(segmentHits.length);
|
|
document.getElementById('metricInvoices').textContent = String(invoiceHits.length);
|
|
document.getElementById('aiSummaryBox').textContent = payload.ai_summary || (customer ? 'Ingen AI-opsummering endnu.' : 'Viser delt arkiv. Vælg kunde eller søg på IP/stiknr/reference for mere præcise fund.');
|
|
document.getElementById('selectedCustomerMeta').textContent = customer
|
|
? `Kunde-ID ${customer.id} · ${customer.name}`
|
|
: 'Delt arkiv uden valgt kunde.';
|
|
|
|
const documentList = document.getElementById('documentList');
|
|
if (!documents.length) {
|
|
documentList.innerHTML = `<div class="wiz-empty">${customer ? 'Ingen kundefiler fundet for denne kunde.' : 'Ingen dokumenter i det delte arkiv endnu.'}</div>`;
|
|
} else {
|
|
documentList.innerHTML = documents.map(item => `
|
|
<div class="wiz-card">
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<h3>${escapeHtml(item.original_filename)}</h3>
|
|
<div class="wiz-meta">${formatDate(item.created_at)} · ${formatBytes(item.file_size)} · ${escapeHtml(item.mime_type || '-')}</div>
|
|
</div>
|
|
<span class="wiz-pill">${item.snippet_count || 0} fund</span>
|
|
</div>
|
|
${(item.notes ? `<div class="wiz-meta mt-2">${escapeHtml(item.notes)}</div>` : '')}
|
|
${(item.snippets || []).map(snippet => `<div class="wiz-snippet">${escapeHtml(snippet)}</div>`).join('')}
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
const segmentList = document.getElementById('segmentList');
|
|
if (!segmentHits.length) {
|
|
segmentList.innerHTML = `<div class="wiz-empty">${customer ? 'Ingen blokke matcher kunden og søgningen endnu.' : 'Ingen blokfund i arkivet endnu.'}</div>`;
|
|
} else {
|
|
segmentList.innerHTML = segmentHits.map(item => `
|
|
<div class="wiz-card">
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<h3>${escapeHtml(item.title || 'Blok')}</h3>
|
|
<div class="wiz-meta">Dokument #${item.document_id} · score ${item.score}</div>
|
|
</div>
|
|
<span class="wiz-pill">blok ${Number(item.block_index || 0) + 1}</span>
|
|
</div>
|
|
<div class="wiz-snippet">${escapeHtml(item.snippet || '-')}</div>
|
|
<div class="wiz-meta mt-2">
|
|
${(item.ip_addresses || []).length ? `IP: ${escapeHtml(item.ip_addresses.join(', '))}` : ''}
|
|
${(item.cidr_blocks || []).length ? `${(item.ip_addresses || []).length ? ' · ' : ''}CIDR: ${escapeHtml(item.cidr_blocks.join(', '))}` : ''}
|
|
${(item.references || []).length ? `${((item.ip_addresses || []).length || (item.cidr_blocks || []).length) ? ' · ' : ''}Ref: ${escapeHtml(item.references.join(', '))}` : ''}
|
|
${(item.socket_numbers || []).length ? `${((item.ip_addresses || []).length || (item.cidr_blocks || []).length || (item.references || []).length) ? ' · ' : ''}Stik: ${escapeHtml(item.socket_numbers.join(', '))}` : ''}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
const invoiceList = document.getElementById('invoiceList');
|
|
if (!invoiceHits.length) {
|
|
invoiceList.innerHTML = `<div class="wiz-empty">${customer ? 'Ingen internetrelaterede fakturafund de sidste 18 måneder.' : 'Vælg kunde for at se relaterede internetfakturaer.'}</div>`;
|
|
} else {
|
|
invoiceList.innerHTML = invoiceHits.map(item => `
|
|
<div class="wiz-card">
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
|
<div>
|
|
<h3>${escapeHtml(item.vendor_name)} · ${escapeHtml(item.invoice_number || '-')}</h3>
|
|
<div class="wiz-meta">${formatDate(item.invoice_date)} · ${formatMoney(item.total_amount, item.currency)}${item.directly_linked ? ' · koblet til kunde' : ''}</div>
|
|
</div>
|
|
<span class="wiz-pill">score ${item.score}</span>
|
|
</div>
|
|
${(item.source_filename ? `<div class="wiz-meta mt-2">Fil: ${escapeHtml(item.source_filename)}</div>` : '')}
|
|
${(item.snippets || []).map(snippet => `<div class="wiz-snippet">${escapeHtml(snippet)}</div>`).join('')}
|
|
</div>
|
|
`).join('');
|
|
}
|
|
}
|
|
|
|
async function uploadCustomerFile() {
|
|
const fileInput = document.getElementById('customerFileInput');
|
|
const notes = document.getElementById('customerFileNotes').value.trim();
|
|
if (!fileInput.files.length) {
|
|
alert('Vælg en fil først.');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
const customerId = document.getElementById('selectedCustomerId').value;
|
|
if (customerId) {
|
|
formData.append('customer_id', customerId);
|
|
}
|
|
formData.append('notes', notes);
|
|
formData.append('file', fileInput.files[0]);
|
|
document.getElementById('uploadStatus').textContent = 'Uploader...';
|
|
|
|
const response = await fetch('/api/v1/internet-connections/customer-documents/upload', {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
const payload = await response.json().catch(() => ({}));
|
|
if (!response.ok) {
|
|
document.getElementById('uploadStatus').textContent = 'Upload fejlede.';
|
|
alert(payload.detail || 'Upload fejlede');
|
|
return;
|
|
}
|
|
|
|
document.getElementById('uploadStatus').textContent = payload.message || `Uploadet: ${payload.filename || fileInput.files[0].name} · ${payload.segment_count || 0} blokke indekseret`;
|
|
localUploadedDocuments.unshift({
|
|
document_id: payload.document_id,
|
|
original_filename: payload.filename || fileInput.files[0].name,
|
|
mime_type: fileInput.files[0].type || 'text/plain',
|
|
file_size: fileInput.files[0].size || 0,
|
|
notes,
|
|
segment_count: payload.segment_count || 0,
|
|
created_at: new Date().toISOString(),
|
|
});
|
|
fileInput.value = '';
|
|
document.getElementById('customerFileNotes').value = '';
|
|
await loadWizardContext();
|
|
}
|
|
|
|
document.getElementById('customerSearchInput').addEventListener('input', () => {
|
|
clearTimeout(customerSearchTimer);
|
|
customerSearchTimer = setTimeout(onCustomerInputChanged, 250);
|
|
});
|
|
|
|
document.getElementById('customerSearchInput').addEventListener('change', onCustomerInputChanged);
|
|
document.getElementById('queryInput').addEventListener('keydown', (event) => {
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
loadWizardContext();
|
|
}
|
|
});
|
|
|
|
loadWizardContext();
|
|
</script>
|
|
{% endblock %}
|