bmc_hub/app/contacts/frontend/contact_detail.html

517 lines
19 KiB
HTML
Raw Normal View History

{% extends "shared/frontend/base.html" %}
{% block title %}Kontakt Detaljer - BMC Hub{% endblock %}
{% block extra_css %}
<style>
.contact-header {
background: var(--accent);
color: white;
padding: 3rem 2rem;
border-radius: 12px;
margin-bottom: 2rem;
}
.contact-avatar-large {
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 2rem;
border: 3px solid rgba(255, 255, 255, 0.3);
}
.nav-pills-vertical {
border-right: 1px solid rgba(0, 0, 0, 0.1);
padding-right: 0;
}
.nav-pills-vertical .nav-link {
color: var(--text-secondary);
border-radius: 8px 0 0 8px;
padding: 1rem 1.5rem;
font-weight: 500;
margin-bottom: 0.5rem;
transition: all 0.2s;
text-align: left;
}
.nav-pills-vertical .nav-link:hover {
background: var(--accent-light);
color: var(--accent);
}
.nav-pills-vertical .nav-link.active {
background: var(--accent);
color: white;
}
.nav-pills-vertical .nav-link i {
width: 20px;
margin-right: 0.75rem;
}
.info-card {
background: var(--bg-card);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
padding: 1.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-weight: 500;
color: var(--text-secondary);
}
.info-value {
color: var(--text-primary);
font-weight: 500;
}
.company-card {
background: var(--bg-card);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
padding: 1.5rem;
transition: all 0.2s;
position: relative;
}
.company-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.remove-company-btn {
position: absolute;
top: 1rem;
right: 1rem;
}
</style>
{% endblock %}
{% block content %}
<!-- Contact Header -->
<div class="contact-header">
<div class="d-flex justify-content-between align-items-start">
<div class="d-flex align-items-center">
<div class="contact-avatar-large me-4" id="contactAvatar">?</div>
<div>
<h1 class="fw-bold mb-2" id="contactName">Loading...</h1>
<div class="d-flex gap-3 align-items-center">
<span id="contactTitle"></span>
<span class="badge bg-white bg-opacity-20" id="contactStatus"></span>
</div>
</div>
</div>
<div class="d-flex gap-2">
<button class="btn btn-light btn-sm" onclick="editContact()">
<i class="bi bi-pencil me-2"></i>Rediger
</button>
<button class="btn btn-light btn-sm" onclick="window.location.href='/contacts'">
<i class="bi bi-arrow-left me-2"></i>Tilbage
</button>
</div>
</div>
</div>
<!-- Content Layout with Sidebar Navigation -->
<div class="row">
<div class="col-lg-3 col-md-4">
<!-- Vertical Navigation -->
<ul class="nav nav-pills nav-pills-vertical flex-column" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#overview">
<i class="bi bi-info-circle"></i>Oversigt
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#companies">
<i class="bi bi-building"></i>Firmaer
</a>
</li>
</ul>
</div>
<div class="col-lg-9 col-md-8">
<!-- Tab Content -->
<div class="tab-content">
<!-- Overview Tab -->
<div class="tab-pane fade show active" id="overview">
<div class="row g-4">
<div class="col-lg-6">
<div class="info-card">
<h5 class="fw-bold mb-4">Kontakt Oplysninger</h5>
<div class="info-row">
<span class="info-label">Navn</span>
<span class="info-value" id="fullName">-</span>
</div>
<div class="info-row">
<span class="info-label">Email</span>
<span class="info-value" id="email">-</span>
</div>
<div class="info-row">
<span class="info-label">Telefon</span>
<span class="info-value" id="phone">-</span>
</div>
<div class="info-row">
<span class="info-label">Mobil</span>
<span class="info-value" id="mobile">-</span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="info-card">
<h5 class="fw-bold mb-4">Rolle & Stilling</h5>
<div class="info-row">
<span class="info-label">Titel</span>
<span class="info-value" id="title">-</span>
</div>
<div class="info-row">
<span class="info-label">Afdeling</span>
<span class="info-value" id="department">-</span>
</div>
<div class="info-row">
<span class="info-label">Status</span>
<span class="info-value" id="activeStatus">-</span>
</div>
<div class="info-row">
<span class="info-label">Antal Firmaer</span>
<span class="info-value" id="companyCount">-</span>
</div>
</div>
</div>
<div class="col-12">
<div class="info-card">
<h5 class="fw-bold mb-3">System Info</h5>
<div class="row">
<div class="col-md-6">
<div class="info-row">
<span class="info-label">vTiger ID</span>
<span class="info-value" id="vtigerId">-</span>
</div>
</div>
<div class="col-md-6">
<div class="info-row">
<span class="info-label">Oprettet</span>
<span class="info-value" id="createdAt">-</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Companies Tab -->
<div class="tab-pane fade" id="companies">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="fw-bold mb-0">Tilknyttede Firmaer</h5>
<button class="btn btn-primary btn-sm" onclick="showAddCompanyModal()">
<i class="bi bi-plus-lg me-2"></i>Tilføj Firma
</button>
</div>
<div class="row g-4" id="companiesContainer">
<div class="col-12 text-center py-5">
<div class="spinner-border text-primary"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add Company Modal -->
<div class="modal fade" id="addCompanyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Tilføj Firma til Kontakt</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addCompanyForm">
<div class="mb-3">
<label class="form-label">Vælg Firma</label>
<select class="form-select" id="companySelectModal" required>
<option value="">Vælg et firma...</option>
<!-- Populated dynamically -->
</select>
</div>
<div class="mb-3">
<label class="form-label">Rolle</label>
<input type="text" class="form-control" id="roleInputModal" placeholder="Primær kontakt, Fakturering...">
</div>
<div class="mb-3">
<label class="form-label">Noter</label>
<textarea class="form-control" id="notesInputModal" rows="3"></textarea>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="isPrimaryInputModal">
<label class="form-check-label" for="isPrimaryInputModal">
Primær kontakt for dette firma
</label>
</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="addCompanyToContact()">
<i class="bi bi-plus-lg me-2"></i>Tilføj
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
const contactId = parseInt(window.location.pathname.split('/').pop());
let contactData = null;
document.addEventListener('DOMContentLoaded', () => {
loadContact();
loadCompaniesForSelect();
// Load companies when tab is shown
document.querySelector('a[href="#companies"]').addEventListener('shown.bs.tab', () => {
loadCompanies();
});
});
async function loadContact() {
try {
const response = await fetch(`/api/v1/contacts/${contactId}`);
if (!response.ok) {
throw new Error('Contact not found');
}
contactData = await response.json();
displayContact(contactData);
} catch (error) {
console.error('Failed to load contact:', error);
alert('Kunne ikke indlæse kontakt');
window.location.href = '/contacts';
}
}
function displayContact(contact) {
// Update page title
document.title = `${contact.first_name} ${contact.last_name} - BMC Hub`;
// Header
const initials = getInitials(contact.first_name, contact.last_name);
document.getElementById('contactAvatar').textContent = initials;
document.getElementById('contactName').textContent = `${contact.first_name} ${contact.last_name}`;
document.getElementById('contactTitle').textContent = contact.title || '';
const statusBadge = contact.is_active
? '<i class="bi bi-check-circle me-1"></i>Aktiv'
: '<i class="bi bi-x-circle me-1"></i>Inaktiv';
document.getElementById('contactStatus').innerHTML = statusBadge;
// Contact Information
document.getElementById('fullName').textContent = `${contact.first_name} ${contact.last_name}`;
document.getElementById('email').textContent = contact.email || '-';
document.getElementById('phone').textContent = contact.phone || '-';
document.getElementById('mobile').textContent = contact.mobile || '-';
// Role & Position
document.getElementById('title').textContent = contact.title || '-';
document.getElementById('department').textContent = contact.department || '-';
document.getElementById('activeStatus').innerHTML = contact.is_active
? '<span class="badge bg-success">Aktiv</span>'
: '<span class="badge bg-secondary">Inaktiv</span>';
document.getElementById('companyCount').textContent = contact.companies ? contact.companies.length : 0;
// System Info
document.getElementById('vtigerId').textContent = contact.vtiger_id || '-';
document.getElementById('createdAt').textContent = new Date(contact.created_at).toLocaleString('da-DK');
// Load companies if tab is active
if (document.querySelector('a[href="#companies"]').classList.contains('active')) {
displayCompanies(contact.companies);
}
}
async function loadCompanies() {
if (!contactData) return;
displayCompanies(contactData.companies);
}
function displayCompanies(companies) {
const container = document.getElementById('companiesContainer');
if (!companies || companies.length === 0) {
container.innerHTML = '<div class="col-12 text-center py-5 text-muted">Ingen firmaer tilknyttet</div>';
return;
}
container.innerHTML = companies.map(company => `
<div class="col-md-6">
<div class="company-card">
<button class="btn btn-sm btn-danger remove-company-btn" onclick="removeCompany(${company.id})" title="Fjern firma">
<i class="bi bi-x-lg"></i>
</button>
<div class="d-flex align-items-start mb-3">
<div class="flex-grow-1">
<h6 class="fw-bold mb-1">
<a href="/customers/${company.id}" class="text-decoration-none">${escapeHtml(company.name)}</a>
</h6>
${company.is_primary ? '<span class="badge bg-primary">Primær Kontakt</span>' : ''}
</div>
</div>
${company.role ? `
<div class="mb-2">
<small class="text-muted">Rolle:</small>
<div>${escapeHtml(company.role)}</div>
</div>
` : ''}
${company.notes ? `
<div>
<small class="text-muted">Noter:</small>
<div class="small">${escapeHtml(company.notes)}</div>
</div>
` : ''}
</div>
</div>
`).join('');
}
async function loadCompaniesForSelect() {
try {
const response = await fetch('/api/v1/customers?limit=1000');
const data = await response.json();
const select = document.getElementById('companySelectModal');
select.innerHTML = '<option value="">Vælg et firma...</option>' +
data.customers.map(c => `<option value="${c.id}">${escapeHtml(c.name)}</option>`).join('');
} catch (error) {
console.error('Failed to load companies:', error);
}
}
function showAddCompanyModal() {
// Reset form
document.getElementById('addCompanyForm').reset();
// Show modal
const modal = new bootstrap.Modal(document.getElementById('addCompanyModal'));
modal.show();
}
async function addCompanyToContact() {
const customerId = parseInt(document.getElementById('companySelectModal').value);
if (!customerId) {
alert('Vælg venligst et firma');
return;
}
const linkData = {
customer_id: customerId,
is_primary: document.getElementById('isPrimaryInputModal').checked,
role: document.getElementById('roleInputModal').value.trim() || null,
notes: document.getElementById('notesInputModal').value.trim() || null
};
try {
const response = await fetch(`/api/v1/contacts/${contactId}/companies`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(linkData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Kunne ikke tilføje firma');
}
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('addCompanyModal'));
modal.hide();
// Reload contact
await loadContact();
// Switch to companies tab
const companiesTab = new bootstrap.Tab(document.querySelector('a[href="#companies"]'));
companiesTab.show();
} catch (error) {
console.error('Failed to add company:', error);
alert('Fejl: ' + error.message);
}
}
async function removeCompany(customerId) {
if (!confirm('Er du sikker på at du vil fjerne dette firma fra kontakten?')) {
return;
}
try {
const response = await fetch(`/api/v1/contacts/${contactId}/companies/${customerId}`, {
method: 'DELETE'
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Kunne ikke fjerne firma');
}
// Reload contact
await loadContact();
} catch (error) {
console.error('Failed to remove company:', error);
alert('Fejl: ' + error.message);
}
}
function editContact() {
// TODO: Open edit modal with pre-filled data
console.log('Edit contact:', contactId);
}
function getInitials(firstName, lastName) {
if (!firstName && !lastName) return '?';
const first = firstName ? firstName[0] : '';
const last = lastName ? lastName[0] : '';
return (first + last).toUpperCase();
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
{% endblock %}