bmc_hub/app/opportunities/frontend/opportunity_detail.html

217 lines
7.8 KiB
HTML
Raw Normal View History

2026-01-28 07:48:10 +01:00
{% extends "shared/frontend/base.html" %}
{% block title %}Mulighed - BMC Hub{% endblock %}
{% block extra_css %}
<style>
.detail-grid {
display: grid;
grid-template-columns: 1.2fr 1.2fr 0.8fr;
gap: 1.5rem;
}
.sticky-panel {
position: sticky;
top: 90px;
}
.section-card {
border: 1px solid rgba(0,0,0,0.06);
border-radius: 12px;
padding: 1.25rem;
background: var(--bg-card);
}
.section-title {
font-weight: 700;
margin-bottom: 1rem;
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-1" id="pageTitle">Mulighed</h2>
<p class="text-muted mb-0">Detaljeret pipelinevisning</p>
</div>
<div class="d-flex gap-2">
<a class="btn btn-outline-secondary" href="/opportunities">
<i class="bi bi-arrow-left me-2"></i>Tilbage
</a>
<button class="btn btn-primary" onclick="saveOpportunity()">
<i class="bi bi-check-lg me-2"></i>Gem
</button>
</div>
</div>
<div class="detail-grid">
<div class="d-flex flex-column gap-3">
<div class="section-card">
<div class="section-title">Grundoplysninger</div>
<div class="mb-3">
<label class="form-label">Titel *</label>
<input type="text" class="form-control" id="title" required>
</div>
<div class="mb-3">
<label class="form-label">Kunde</label>
<input type="text" class="form-control" id="customerName" disabled>
</div>
<div class="mb-0">
<label class="form-label">Beskrivelse</label>
<textarea class="form-control" id="description" rows="4"></textarea>
</div>
</div>
<div class="section-card">
<div class="section-title">Salgsstatus</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Stage</label>
<select class="form-select" id="stageId"></select>
</div>
<div class="col-md-6">
<label class="form-label">Sandsynlighed</label>
<input type="text" class="form-control" id="probability" disabled>
</div>
<div class="col-md-6">
<label class="form-label">Forventet lukning</label>
<input type="date" class="form-control" id="expectedCloseDate">
</div>
</div>
</div>
</div>
<div class="d-flex flex-column gap-3">
<div class="section-card">
<div class="section-title">Løsning & Salgsdetaljer</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Beløb</label>
<input type="number" step="0.01" class="form-control" id="amount">
</div>
<div class="col-md-6">
<label class="form-label">Valuta</label>
<select class="form-select" id="currency">
<option value="DKK">DKK</option>
<option value="EUR">EUR</option>
<option value="USD">USD</option>
</select>
</div>
</div>
</div>
<div class="section-card">
<div class="section-title">Tilbud & Kontrakt</div>
<div class="text-muted small">Felt til dokumentlink og kontraktstatus kommer i næste version.</div>
</div>
</div>
<div class="sticky-panel d-flex flex-column gap-3">
<div class="section-card">
<div class="section-title">Pipelinestatus</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Kunde</span>
<span id="customerNameBadge">-</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Stage</span>
<span id="stageBadge">-</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Værdi</span>
<span id="amountBadge">-</span>
</div>
<div class="d-flex justify-content-between">
<span class="text-muted">Sandsynlighed</span>
<span id="probabilityBadge">-</span>
</div>
</div>
<div class="section-card">
<div class="section-title">Næste aktivitet</div>
<div class="text-muted small">Aktivitetsmodul kommer senere.</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
const opportunityId = parseInt(window.location.pathname.split('/').pop());
let stages = [];
let opportunity = null;
document.addEventListener('DOMContentLoaded', async () => {
await loadStages();
await loadOpportunity();
});
async function loadStages() {
const response = await fetch('/api/v1/pipeline/stages');
stages = await response.json();
const select = document.getElementById('stageId');
select.innerHTML = stages.map(s => `<option value="${s.id}">${s.name}</option>`).join('');
}
async function loadOpportunity() {
const response = await fetch(`/api/v1/opportunities/${opportunityId}`);
if (!response.ok) {
alert('Mulighed ikke fundet');
window.location.href = '/opportunities';
return;
}
opportunity = await response.json();
renderOpportunity();
}
function renderOpportunity() {
document.getElementById('pageTitle').textContent = opportunity.title;
document.getElementById('title').value = opportunity.title;
document.getElementById('customerName').value = opportunity.customer_name || '-';
document.getElementById('description').value = opportunity.description || '';
document.getElementById('amount').value = opportunity.amount || 0;
document.getElementById('currency').value = opportunity.currency || 'DKK';
document.getElementById('expectedCloseDate').value = opportunity.expected_close_date || '';
document.getElementById('stageId').value = opportunity.stage_id;
document.getElementById('probability').value = `${opportunity.probability || 0}%`;
document.getElementById('customerNameBadge').textContent = opportunity.customer_name || '-';
document.getElementById('stageBadge').textContent = opportunity.stage_name || '-';
document.getElementById('amountBadge').textContent = formatCurrency(opportunity.amount, opportunity.currency);
document.getElementById('probabilityBadge').textContent = `${opportunity.probability || 0}%`;
}
async function saveOpportunity() {
const payload = {
title: document.getElementById('title').value,
description: document.getElementById('description').value || null,
amount: parseFloat(document.getElementById('amount').value || 0),
currency: document.getElementById('currency').value,
expected_close_date: document.getElementById('expectedCloseDate').value || null,
stage_id: parseInt(document.getElementById('stageId').value)
};
const response = await fetch(`/api/v1/opportunities/${opportunityId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
alert('Kunne ikke gemme mulighed');
return;
}
opportunity = await response.json();
renderOpportunity();
}
function formatCurrency(value, currency) {
const num = parseFloat(value || 0);
return new Intl.NumberFormat('da-DK', { style: 'currency', currency: currency || 'DKK' }).format(num);
}
</script>
{% endblock %}