bmc_hub/app/opportunities/frontend/opportunity_detail.html
2026-01-28 07:48:10 +01:00

217 lines
7.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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 %}