bmc_hub/app/vendors/frontend/vendors.html

415 lines
15 KiB
HTML
Raw Permalink Normal View History

{% extends "shared/frontend/base.html" %}
{% block title %}Leverandører - BMC Hub{% endblock %}
{% block extra_css %}
<style>
.filter-btn {
background: var(--bg-card);
border: 1px solid rgba(0,0,0,0.1);
color: var(--text-secondary);
padding: 0.5rem 1.2rem;
border-radius: 20px;
font-size: 0.9rem;
transition: all 0.2s;
cursor: pointer;
}
.filter-btn:hover, .filter-btn.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.vendor-avatar {
width: 40px;
height: 40px;
border-radius: 8px;
background: var(--accent-light);
color: var(--accent);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.9rem;
}
.category-badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 500;
}
.priority-badge {
width: 30px;
height: 30px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.75rem;
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-5">
<div>
<h2 class="fw-bold mb-1">Leverandører</h2>
<p class="text-muted mb-0">Administrer dine leverandører og partnere</p>
</div>
<div class="d-flex gap-3">
<input type="text" id="searchInput" class="header-search" placeholder="Søg leverandør, CVR, domain...">
<button class="btn btn-primary" onclick="showCreateVendorModal()">
<i class="bi bi-plus-lg me-2"></i>Opret Leverandør
</button>
</div>
</div>
<div class="mb-4 d-flex gap-2 flex-wrap">
<button class="filter-btn active" data-filter="all" onclick="setFilter('all')">
Alle <span id="countAll" class="ms-1"></span>
</button>
<button class="filter-btn" data-filter="hardware" onclick="setFilter('hardware')">
Hardware
</button>
<button class="filter-btn" data-filter="software" onclick="setFilter('software')">
Software
</button>
<button class="filter-btn" data-filter="telecom" onclick="setFilter('telecom')">
Telekom
</button>
<button class="filter-btn" data-filter="services" onclick="setFilter('services')">
Services
</button>
</div>
<div class="card p-4">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Leverandør</th>
<th>Kontakt Info</th>
<th>CVR</th>
<th>Kategori</th>
<th>Status</th>
<th class="text-end">Handlinger</th>
</tr>
</thead>
<tbody id="vendorsTableBody">
<tr>
<td colspan="6" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="d-flex justify-content-between align-items-center mt-4">
<div class="text-muted small">
Viser <span id="showingStart">0</span>-<span id="showingEnd">0</span> af <span id="totalCount">0</span> leverandører
</div>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-secondary" id="prevBtn" onclick="previousPage()">
<i class="bi bi-chevron-left"></i> Forrige
</button>
<button class="btn btn-sm btn-outline-secondary" id="nextBtn" onclick="nextPage()">
Næste <i class="bi bi-chevron-right"></i>
</button>
</div>
</div>
</div>
<!-- Create Vendor Modal -->
<div class="modal fade" id="createVendorModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Opret Ny Leverandør</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="createVendorForm">
<div class="row g-3">
<div class="col-md-8">
<label class="form-label">Virksomhedsnavn *</label>
<input type="text" class="form-control" id="name" required>
</div>
<div class="col-md-4">
<label class="form-label">CVR-nummer</label>
<input type="text" class="form-control" id="cvr_number" maxlength="8">
</div>
<div class="col-md-6">
<label class="form-label">Email</label>
<input type="email" class="form-control" id="email">
</div>
<div class="col-md-6">
<label class="form-label">Telefon</label>
<input type="text" class="form-control" id="phone">
</div>
<div class="col-md-6">
<label class="form-label">Website</label>
<input type="url" class="form-control" id="website">
</div>
<div class="col-md-6">
<label class="form-label">Domain</label>
<input type="text" class="form-control" id="domain" placeholder="example.com">
</div>
<div class="col-12">
<label class="form-label">Adresse</label>
<input type="text" class="form-control" id="address">
</div>
<div class="col-md-3">
<label class="form-label">Postnummer</label>
<input type="text" class="form-control" id="postal_code">
</div>
<div class="col-md-5">
<label class="form-label">By</label>
<input type="text" class="form-control" id="city">
</div>
<div class="col-md-4">
<label class="form-label">Kategori</label>
<select class="form-select" id="category">
<option value="general">General</option>
<option value="hardware">Hardware</option>
<option value="software">Software</option>
<option value="telecom">Telekom</option>
<option value="services">Services</option>
<option value="hosting">Hosting</option>
</select>
</div>
<div class="col-12">
<label class="form-label">Noter</label>
<textarea class="form-control" id="notes" rows="3"></textarea>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuller</button>
<button type="button" class="btn btn-primary" onclick="createVendor()">
<i class="bi bi-check-lg me-2"></i>Opret Leverandør
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let currentPage = 0;
let pageSize = 50;
let currentFilter = 'all';
let searchTerm = '';
async function loadVendors() {
try {
const params = new URLSearchParams({
skip: currentPage * pageSize,
limit: pageSize
});
if (searchTerm) {
params.append('search', searchTerm);
}
if (currentFilter !== 'all') {
params.append('category', currentFilter);
}
console.log('🔄 Loading vendors from:', `/api/v1/vendors?${params}`);
const response = await fetch(`/api/v1/vendors?${params}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const vendors = await response.json();
console.log('✅ Loaded vendors:', vendors.length);
displayVendors(vendors);
updatePagination(vendors.length);
} catch (error) {
console.error('❌ Error loading vendors:', error);
document.getElementById('vendorsTableBody').innerHTML = `
<tr><td colspan="7" class="text-center text-danger py-5">
<i class="bi bi-exclamation-triangle fs-2 d-block mb-2"></i>
<strong>Kunne ikke indlæse leverandører</strong><br>
<small class="text-muted">${error.message}</small>
</td></tr>
`;
}
}
function displayVendors(vendors) {
const tbody = document.getElementById('vendorsTableBody');
if (vendors.length === 0) {
tbody.innerHTML = `
<tr><td colspan="7" class="text-center text-muted py-5">
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
Ingen leverandører fundet
</td></tr>
`;
return;
}
tbody.innerHTML = vendors.map(vendor => `
<tr onclick="window.location.href='/vendors/${vendor.id}'" style="cursor: pointer;">
<td>
<div class="d-flex align-items-center gap-3">
<div class="vendor-avatar">${getInitials(vendor.name)}</div>
<div>
<div class="fw-semibold">${escapeHtml(vendor.name)}</div>
${vendor.domain ? `<small class="text-muted">${escapeHtml(vendor.domain)}</small>` : ''}
</div>
</div>
</td>
<td>
${vendor.email ? `<div><i class="bi bi-envelope me-2"></i>${escapeHtml(vendor.email)}</div>` : ''}
${vendor.phone ? `<div><i class="bi bi-telephone me-2"></i>${escapeHtml(vendor.phone)}</div>` : ''}
${!vendor.email && !vendor.phone ? '<span class="text-muted">-</span>' : ''}
</td>
<td>${vendor.cvr_number ? escapeHtml(vendor.cvr_number) : '<span class="text-muted">-</span>'}</td>
<td>
<span class="category-badge bg-light">
${getCategoryIcon(vendor.category)} ${escapeHtml(vendor.category)}
</span>
</td>
<td>
<span class="badge ${vendor.is_active ? 'bg-success' : 'bg-secondary'}">
${vendor.is_active ? 'Aktiv' : 'Inaktiv'}
</span>
</td>
<td class="text-end">
<button class="btn btn-sm btn-light" onclick="event.stopPropagation(); editVendor(${vendor.id})">
<i class="bi bi-pencil"></i>
</button>
</td>
</tr>
`).join('');
}
function getCategoryIcon(category) {
const icons = {
hardware: '🖥️',
software: '💻',
telecom: '📡',
services: '🛠️',
hosting: '☁️',
general: '📦'
};
return icons[category] || '📦';
}
function getInitials(name) {
return name.split(' ').map(word => word[0]).join('').substring(0, 2).toUpperCase();
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function setFilter(filter) {
currentFilter = filter;
currentPage = 0;
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.filter === filter);
});
loadVendors();
}
function updatePagination(count) {
document.getElementById('showingStart').textContent = currentPage * pageSize + 1;
document.getElementById('showingEnd').textContent = currentPage * pageSize + count;
document.getElementById('totalCount').textContent = count;
document.getElementById('prevBtn').disabled = currentPage === 0;
document.getElementById('nextBtn').disabled = count < pageSize;
}
function previousPage() {
if (currentPage > 0) {
currentPage--;
loadVendors();
}
}
function nextPage() {
currentPage++;
loadVendors();
}
function showCreateVendorModal() {
const modal = new bootstrap.Modal(document.getElementById('createVendorModal'));
modal.show();
}
async function createVendor() {
const form = document.getElementById('createVendorForm');
const vendor = {
name: document.getElementById('name').value,
cvr_number: document.getElementById('cvr_number').value || null,
email: document.getElementById('email').value || null,
phone: document.getElementById('phone').value || null,
website: document.getElementById('website').value || null,
domain: document.getElementById('domain').value || null,
address: document.getElementById('address').value || null,
postal_code: document.getElementById('postal_code').value || null,
city: document.getElementById('city').value || null,
category: document.getElementById('category').value,
priority: parseInt(document.getElementById('priority').value),
notes: document.getElementById('notes').value || null,
is_active: true
};
try {
const response = await fetch('/api/v1/vendors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(vendor)
});
if (response.ok) {
bootstrap.Modal.getInstance(document.getElementById('createVendorModal')).hide();
form.reset();
loadVendors();
} else {
alert('Fejl ved oprettelse af leverandør');
}
} catch (error) {
console.error('Error creating vendor:', error);
alert('Kunne ikke oprette leverandør');
}
}
// Search
let vendorSearchTimeout;
document.getElementById('searchInput').addEventListener('input', (e) => {
clearTimeout(vendorSearchTimeout);
vendorSearchTimeout = setTimeout(() => {
searchTerm = e.target.value;
currentPage = 0;
loadVendors();
}, 300);
});
// Load on page ready
document.addEventListener('DOMContentLoaded', loadVendors);
</script>
{% endblock %}