289 lines
11 KiB
HTML
289 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="da">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>BMC Office Abonnementer - Upload</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
|
<style>
|
|
:root {
|
|
--primary: #0f4c75;
|
|
--accent: #3282b8;
|
|
}
|
|
|
|
body {
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.upload-zone {
|
|
border: 3px dashed var(--accent);
|
|
border-radius: 15px;
|
|
padding: 60px 20px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
background: white;
|
|
}
|
|
|
|
.upload-zone:hover {
|
|
border-color: var(--primary);
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.upload-zone.dragover {
|
|
background: #e3f2fd;
|
|
border-color: var(--primary);
|
|
transform: scale(1.02);
|
|
}
|
|
|
|
.stats-card {
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-item {
|
|
text-align: center;
|
|
padding: 15px;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 2.5rem;
|
|
font-weight: bold;
|
|
color: var(--primary);
|
|
}
|
|
|
|
.stat-label {
|
|
color: #6c757d;
|
|
font-size: 0.9rem;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.progress-container {
|
|
display: none;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.error-list {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container py-5">
|
|
<div class="row">
|
|
<div class="col-lg-8 mx-auto">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2 class="fw-bold">
|
|
<i class="bi bi-cloud-upload text-primary me-2"></i>
|
|
BMC Office Abonnementer
|
|
</h2>
|
|
<a href="/customers" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left me-2"></i>Tilbage
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Info Card -->
|
|
<div class="alert alert-info">
|
|
<h5 class="alert-heading">
|
|
<i class="bi bi-info-circle me-2"></i>Om Upload
|
|
</h5>
|
|
<p class="mb-0">Upload en Excel fil (.xlsx eller .xls) med BMC Office abonnementsdata.
|
|
Systemet vil automatisk matche firma navne med kunder i databasen.</p>
|
|
<hr>
|
|
<small class="text-muted">
|
|
<strong>Forventede kolonner:</strong> FirmaID, Firma, Startdate, Text, Antal, Pris, Rabat, Beskrivelse, FakturaFirmaID, FakturaFirma
|
|
</small>
|
|
</div>
|
|
|
|
<!-- Upload Zone -->
|
|
<div class="upload-zone" id="uploadZone">
|
|
<i class="bi bi-cloud-arrow-up" style="font-size: 4rem; color: var(--accent);"></i>
|
|
<h4 class="mt-3">Træk Excel fil hertil</h4>
|
|
<p class="text-muted">eller klik for at vælge fil</p>
|
|
<input type="file" id="fileInput" accept=".xlsx,.xls" style="display: none;">
|
|
<button class="btn btn-primary mt-3" onclick="document.getElementById('fileInput').click()">
|
|
<i class="bi bi-folder2-open me-2"></i>Vælg Fil
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Progress -->
|
|
<div class="progress-container" id="progressContainer">
|
|
<div class="progress" style="height: 30px;">
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
|
role="progressbar"
|
|
id="progressBar"
|
|
style="width: 0%">
|
|
<span id="progressText">Uploader...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results -->
|
|
<div id="resultsContainer" class="mt-4" style="display: none;">
|
|
<!-- Statistics -->
|
|
<div class="stats-card">
|
|
<h5 class="fw-bold mb-4">
|
|
<i class="bi bi-bar-chart text-success me-2"></i>Import Resultat
|
|
</h5>
|
|
<div class="row text-center">
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<div class="stat-number text-primary" id="statImported">0</div>
|
|
<div class="stat-label">Importeret</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<div class="stat-number text-warning" id="statSkipped">0</div>
|
|
<div class="stat-label">Sprunget Over</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<div class="stat-number text-success" id="statActive">0</div>
|
|
<div class="stat-label">Aktive</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-item">
|
|
<div class="stat-number text-info" id="statValue">0</div>
|
|
<div class="stat-label">DKK (Total)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Errors (if any) -->
|
|
<div id="errorsCard" class="stats-card" style="display: none;">
|
|
<h5 class="fw-bold text-danger mb-3">
|
|
<i class="bi bi-exclamation-triangle me-2"></i>Advarsler
|
|
</h5>
|
|
<div class="error-list" id="errorList"></div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="text-center mt-4">
|
|
<button class="btn btn-primary btn-lg" onclick="location.reload()">
|
|
<i class="bi bi-arrow-clockwise me-2"></i>Upload Ny Fil
|
|
</button>
|
|
<a href="/customers" class="btn btn-outline-secondary btn-lg">
|
|
<i class="bi bi-people me-2"></i>Gå til Kunder
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const uploadZone = document.getElementById('uploadZone');
|
|
const fileInput = document.getElementById('fileInput');
|
|
const progressContainer = document.getElementById('progressContainer');
|
|
const progressBar = document.getElementById('progressBar');
|
|
const progressText = document.getElementById('progressText');
|
|
const resultsContainer = document.getElementById('resultsContainer');
|
|
|
|
// Drag & Drop
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
uploadZone.addEventListener(eventName, preventDefaults, false);
|
|
});
|
|
|
|
function preventDefaults(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
|
|
['dragenter', 'dragover'].forEach(eventName => {
|
|
uploadZone.addEventListener(eventName, () => {
|
|
uploadZone.classList.add('dragover');
|
|
});
|
|
});
|
|
|
|
['dragleave', 'drop'].forEach(eventName => {
|
|
uploadZone.addEventListener(eventName, () => {
|
|
uploadZone.classList.remove('dragover');
|
|
});
|
|
});
|
|
|
|
uploadZone.addEventListener('drop', handleDrop);
|
|
fileInput.addEventListener('change', handleFileSelect);
|
|
|
|
function handleDrop(e) {
|
|
const dt = e.dataTransfer;
|
|
const files = dt.files;
|
|
if (files.length > 0) {
|
|
uploadFile(files[0]);
|
|
}
|
|
}
|
|
|
|
function handleFileSelect(e) {
|
|
const files = e.target.files;
|
|
if (files.length > 0) {
|
|
uploadFile(files[0]);
|
|
}
|
|
}
|
|
|
|
async function uploadFile(file) {
|
|
if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) {
|
|
alert('Kun Excel filer (.xlsx, .xls) er tilladt');
|
|
return;
|
|
}
|
|
|
|
uploadZone.style.display = 'none';
|
|
progressContainer.style.display = 'block';
|
|
progressBar.style.width = '50%';
|
|
progressText.textContent = 'Uploader og importerer...';
|
|
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
try {
|
|
const response = await fetch('/api/v1/admin/bmc-office-subscriptions/upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
progressBar.style.width = '100%';
|
|
progressText.textContent = 'Færdig!';
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(result.detail || 'Upload fejlede');
|
|
}
|
|
|
|
displayResults(result);
|
|
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
alert('Fejl ved upload: ' + error.message);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function displayResults(result) {
|
|
progressContainer.style.display = 'none';
|
|
resultsContainer.style.display = 'block';
|
|
|
|
document.getElementById('statImported').textContent = result.imported;
|
|
document.getElementById('statSkipped').textContent = result.skipped;
|
|
document.getElementById('statActive').textContent = result.statistics.active_records;
|
|
document.getElementById('statValue').textContent = result.statistics.total_value_dkk.toFixed(2);
|
|
|
|
if (result.errors && result.errors.length > 0) {
|
|
document.getElementById('errorsCard').style.display = 'block';
|
|
const errorList = document.getElementById('errorList');
|
|
errorList.innerHTML = result.errors.map(err =>
|
|
`<div class="alert alert-warning py-2 mb-2"><small>${err}</small></div>`
|
|
).join('');
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|