217 lines
7.8 KiB
HTML
217 lines
7.8 KiB
HTML
{% 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 pipeline‑visning</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">Pipeline‑status</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 %}
|