js/deals.js.bak_20260428_195534_active

Code: DEV-1E808B26 Size: 29.5 KB Lines: 478 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/js/deals.js.bak_20260428_195534_active

Task / Comment

Open report form
// projekt.js - Deals, pipeline, filters

// Returnerar URL-parameter '&saljare_id=X' om användaren är begränsad till egna affärer.
function _dealsScopeParam(){
    var scope = (typeof getRoleSetting === 'function') ? getRoleSetting('projekt.scope') : null;
    if (!scope) {
        var role = (window.gUserRole || sessionStorage.getItem('gUserRole') || '');
        scope = (['admin','systemadmin','saljchef','ekonomi'].indexOf(role) >= 0) ? 'all' : 'own';
    }
    if (scope === 'all') return '';
    var sid = parseInt(window.gStaffId || sessionStorage.getItem('gStaffId') || 0, 10);
    return sid ? ('&saljare_id=' + sid) : '';
}

function setDealView(v) {
    dealView = v;
    loadDeals();
}

var dealsLoaded = false;
var allDealsCache = [];

async function loadDeals() {
    if (!dealsLoaded) {
        try {
            var r = await fetch('api/deals.php?limit=' + DEAL_LIMIT + '&t=' + Date.now() + _dealsScopeParam());
            var data = await r.json();
            allDealsCache = data.deals || [];
            populateDealFilters(allDealsCache);
            dealsLoaded = true;
        } catch(e) { console.error('loadDeals error:', e); return; }
    }
    applyDealFilters();
    loadDealStats();
}

function reloadDeals() {
    applyDealFilters();
}

// Snabbfilter via pills (Offert/Order/Pågående/Klart/Ånger)
var _dealActiveQuick = null;
var _dealQuickStatusMap = {
    offert:   ['offert'],
    order:    ['order'],
    pagaende: ['projektering','bestallning','leverans','montering','besiktning'],
    fardig:   ['fardigstall'],
    lost:     ['anger','ej_godkand','avbrott']
};

function setDealQuickFilter(key){
    _dealActiveQuick = (_dealActiveQuick === key) ? null : key;
    // Återställ status-selecten — pillen tar över
    var sel = document.getElementById('dealFilterStatus');
    if(sel) sel.value = '';
    _renderDealPillState();
    applyDealFilters();
}
window.setDealQuickFilter = setDealQuickFilter;

function _renderDealPillState(){
    var ids = { offert:'dPillOffert', order:'dPillOrder', pagaende:'dPillPagaende', fardig:'dPillFardig', lost:'dPillLost' };
    Object.keys(ids).forEach(function(k){
        var el = document.getElementById(ids[k]);
        if(!el) return;
        if(_dealActiveQuick === k){
            el.style.boxShadow = '0 0 0 2px #024550';
            el.style.transform = 'scale(1.03)';
        } else {
            el.style.boxShadow = '';
            el.style.transform = '';
        }
    });
}

function applyDealFilters() {
    var status = document.getElementById('dealFilterStatus').value;
    var product = document.getElementById('dealFilterProduct').value;
    var month = document.getElementById('dealFilterMonth') ? document.getElementById('dealFilterMonth').value : '';
    var saljStatus = document.getElementById('dealFilterSaljStatus') ? document.getElementById('dealFilterSaljStatus').value : '';
    var region = document.getElementById('dealFilterRegion') ? document.getElementById('dealFilterRegion').value : '';
    var seller = document.getElementById('dealFilterSeller') ? document.getElementById('dealFilterSeller').value : '';
    var quickStatuses = _dealActiveQuick ? _dealQuickStatusMap[_dealActiveQuick] : null;

    allDeals = allDealsCache.filter(function(d) {
        if (status && d.status !== status) return false;
        if (quickStatuses && quickStatuses.indexOf(d.status) < 0) return false;
        if (product && !(d.product_types || '').split(',').includes(product)) return false;
        if (month && !(d.datum_salj || '').startsWith(month)) return false;
        if (saljStatus && d.salj_status !== saljStatus) return false;
        if (region && d.region !== region) return false;
        if (seller && String(d.saljare1_id) !== seller && String(d.saljare2_id) !== seller) return false;
        return true;
    });
    dealTotal = allDeals.length;
    buildMainDealsDT(allDeals);
    // Pills ska reflektera samma filter (månad + säljare) — refresh stats
    if (typeof loadDealStats === 'function') loadDealStats();
}

function populateDealFilters(deals) {
    var months = {};
    var regions = {};
    var sellers = {}; // id => name
    deals.forEach(function(d) {
        var ds = d.datum_salj || '';
        if (ds.length >= 7) months[ds.substring(0, 7)] = true;
        if (d.region) regions[d.region] = true;
        if (d.saljare1_id && d.saljare1_name) sellers[d.saljare1_id] = d.saljare1_name;
        if (d.saljare2_id && d.saljare2_name) sellers[d.saljare2_id] = d.saljare2_name;
    });
    // Säljar-filter — visas bara om >1 unik säljare
    var sellerSel = document.getElementById('dealFilterSeller');
    if (sellerSel) {
        var sellerKeys = Object.keys(sellers);
        if (sellerKeys.length > 1) {
            var oldSel = sellerSel.value;
            sellerSel.innerHTML = '<option value="">Alla säljare</option>'
                + sellerKeys.sort(function(a,b){ return sellers[a].localeCompare(sellers[b]); })
                  .map(function(id){ return '<option value="'+id+'">'+sellers[id]+'</option>'; }).join('');
            sellerSel.value = oldSel;
            sellerSel.style.display = '';
        } else {
            sellerSel.style.display = 'none';
            sellerSel.value = '';
        }
    }
    // Month filter
    var keys = Object.keys(months).sort().reverse();
    var sel = document.getElementById('dealFilterMonth');
    if (sel) {
        var old = sel.value;
        sel.innerHTML = '<option value="">Alla månader</option>';
        var mNames = ['','Jan','Feb','Mar','Apr','Maj','Jun','Jul','Aug','Sep','Okt','Nov','Dec'];
        keys.forEach(function(m) {
            var p = m.split('-');
            sel.innerHTML += '<option value="' + m + '">' + p[0] + ' ' + mNames[parseInt(p[1])] + '</option>';
        });
        // Default = nuvarande månad (om inte användaren själv valt något annat)
        var _today = new Date();
        var _curMonth = _today.getFullYear() + '-' + String(_today.getMonth()+1).padStart(2,'0');
        if (old) {
            sel.value = old;
        } else if (Array.from(sel.options).some(function(o){ return o.value === _curMonth; })) {
            sel.value = _curMonth;
        }
    }
    // Region filter
    var regSel = document.getElementById('dealFilterRegion');
    if (regSel) {
        var oldR = regSel.value;
        regSel.innerHTML = '<option value="">Alla regioner</option>';
        Object.keys(regions).sort().forEach(function(r) {
            regSel.innerHTML += '<option value="' + r + '">' + r + '</option>';
        });
        if (oldR) regSel.value = oldR;
    }
}

function buildMainDealsDT(deals) {
    var rows = deals.map(function(d) {
        var products = (d.product_types||'').split(',').filter(Boolean).map(function(p){ return PRODUCT_LABELS[p]||p; }).join(', ');
        return [
            d.id,
            d.deal_number || '',
            d.customer_name || '',
            products || '',
            parseFloat(d.ordervarde_ink_moms || 0),
            d.saljare1_name || '',
            d.datum_salj || '',
            d.status || '',
            d.salj_status || '',
            d.status_order || '',
            d.region || ''
        ];
    });

    if (mainDealsDT) { mainDealsDT.destroy(); mainDealsDT = null; }
    mainDealsDT = $('#dealsDT').DataTable({
        data: rows,
        columns: [
            { title:'Affärsnr', render: function(d,t,r){ return '<strong style="color:#024550;white-space:nowrap">' + r[1] + '</strong>'; } },
            { title:'Kund', render: function(d,t,r){ return r[2]; } },
            { title:'Produkter', render: function(d,t,r){ return '<span style="font-size:12px;color:#64748b">' + (r[3]||'-') + '</span>'; } },
            { title:'Ordervärde', className:'dt-right', render: function(d,t,r){
                if (t === 'sort' || t === 'type') return r[4];
                return r[4] ? r[4].toLocaleString('sv-SE') + ' kr' : '-';
            }},
            { title:'Säljare', render: function(d,t,r){ return '<span style="font-size:12px">' + (r[5]||'-') + '</span>'; } },
            { title:'Datum', render: function(d,t,r){
                if (t === 'sort' || t === 'type') return r[6];
                return '<span style="font-size:12px;color:#64748b">' + (r[6]||'-') + '</span>';
            }, width:'90px' },
            { title:'Status', render: function(d,t,r){
                var color = DEAL_STATUS_COLORS[r[7]] || '#94a3b8';
                var label = DEAL_STATUS_LABELS[r[7]] || r[7] || '-';
                if (t === 'filter') return label;
                return '<span style="font-size:10px;padding:2px 8px;border-radius:10px;color:#fff;background:'+color+'">'+label+'</span>';
            } },
            { title:'Status sälj', render: function(d,t,r){
                var color = SALJ_STATUS_COLORS[r[8]] || '#c4c4c4';
                var label = SALJ_STATUS_LABELS[r[8]] || r[8] || '-';
                if (t === 'filter') return label;
                return '<span style="font-size:10px;padding:2px 8px;border-radius:10px;color:#fff;background:'+color+'">'+label+'</span>';
            } },
            { title:'Status order', render: function(d,t,r){
                if (t === 'filter') return r[9] || '-';
                if (!r[9]) return '<span style="font-size:11px;color:#cbd5e1">-</span>';
                return '<span style="font-size:11px;font-weight:600;color:#334155">' + r[9] + '</span>';
            } },
            { title:'Region', render: function(d,t,r){
                if (t === 'filter') return r[10] || '-';
                if (!r[10]) return '';
                return '<span style="font-size:11px;color:#64748b">' + r[10] + '</span>';
            } }
        ],
        language: {
            search:'Sök:', lengthMenu:'Visa _MENU_ per sida',
            info:'Visar _START_-_END_ av _TOTAL_ affärer', infoEmpty:'Inga affärer',
            infoFiltered:'(filtrerat från _MAX_ totalt)',
            paginate:{first:'Första',last:'Sista',next:'Nästa',previous:'Föreg.'},
            zeroRecords:'Inga affärer hittades'
        },
        pageLength: 50,
        lengthMenu: [25, 50, 100, 200],
        order: [[5,'desc']],
        createdRow: function(row, data) {
            $(row).css('cursor','pointer').on('click', function(){ showDealDetail(data[0]); });
        }
    });
}

async function loadDealStats() {
    try {
        var monthEl = document.getElementById('dealFilterMonth');
        var sellerEl = document.getElementById('dealFilterSeller');
        var monthV = monthEl && monthEl.value ? '&month=' + encodeURIComponent(monthEl.value) : '';
        // Säljar-filter overrider scope (admin kan filtrera ner till en specifik säljare)
        var sellerV = sellerEl && sellerEl.value ? '&saljare_id=' + encodeURIComponent(sellerEl.value) : _dealsScopeParam();
        const r = await fetch('api/deals.php?action=stats&t='+Date.now() + monthV + sellerV);
        const stats = await r.json();
        const el = (id,v) => { const e=document.getElementById(id); if(e) e.textContent=v; };
        const sum = (keys) => keys.reduce((a,k) => a + (stats[k]?.count||0), 0);
        const sumV = (keys) => keys.reduce((a,k) => a + (stats[k]?.value||0), 0);
        el('dStatOffert', stats.offert?.count||0);
        el('dSumOffert', fmtKr(stats.offert?.value||0));
        el('dStatOrder', stats.order?.count||0);
        el('dSumOrder', fmtKr(stats.order?.value||0));
        const pKeys = ['projektering','bestallning','leverans','montering','besiktning'];
        el('dStatPagaende', sum(pKeys));
        el('dSumPagaende', fmtKr(sumV(pKeys)));
        el('dStatFardig', stats.fardigstall?.count||0);
        el('dSumFardig', fmtKr(stats.fardigstall?.value||0));
        const lKeys = ['anger','ej_godkand','avbrott'];
        el('dStatLost', sum(lKeys));
        el('dSumLost', fmtKr(sumV(lKeys)));
    } catch(e) { console.error('loadDealStats error:', e); }
}

async function showDealDetail(id) {
    try {
        const r = await fetch('api/deals.php?id='+id+'&t='+Date.now());
        const d = await r.json();
        if (d.error) { alert(d.error); return; }

        const products = (d.products||[]).map(p => PRODUCT_LABELS[p.product_type]||p.product_type).join(', ');
        const contacts = (d.contact_methods||[]).map(c => {
            var icon = c.type==='phone'?'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>':'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>';
            return '<div style="display:flex;gap:6px;align-items:center;font-size:13px;color:#334155">'+icon+' <strong>'+c.value+'</strong>'+(c.label?' <span style="font-size:11px;color:#94a3b8">('+c.label+')</span>':'')+'</div>';
        }).join('');

        // Pipeline progress bar
        const stages = ['offert','order','projektering','bestallning','leverans','montering','besiktning','fardigstall'];
        const currentIdx = stages.indexOf(d.status);
        const isNeg = ['anger','ej_godkand','avbrott'].includes(d.status);
        const pipelineHtml = '<div style="display:flex;gap:3px;margin:0">' + stages.map((s,i) => {
            const active = !isNeg && i <= currentIdx;
            const color = active ? DEAL_STATUS_COLORS[s] : '#e5e7eb';
            return '<div style="flex:1;text-align:center">'
                +'<div style="height:8px;background:'+color+';border-radius:4px"></div>'
                +'<div style="font-size:10px;color:'+(active?'#334155':'#cbd5e1')+';margin-top:4px;font-weight:'+(active?'600':'400')+'">'+DEAL_STATUS_LABELS[s]+'</div></div>';
        }).join('') + '</div>';

        // Activity timeline
        var timelineItems = [];
        if (d.datum_salj) timelineItems.push({date:d.datum_salj, label:'Sälj', color:'#eab308', icon:'S'});
        if (d.projektering_bokad) timelineItems.push({date:d.projektering_bokad, label:'Projektering bokad', color:'#3b82f6', icon:'P'});
        if (d.datum_fardigstall) timelineItems.push({date:d.datum_fardigstall, label:'Färdigställt', color:'#10b981', icon:'F'});
        if (d.datum_avbrott) timelineItems.push({date:d.datum_avbrott, label:'Avbrott', color:'#ef4444', icon:'A'});
        // Add status log entries
        (d.status_log||[]).forEach(function(l) {
            timelineItems.push({date:l.created_at, label:(DEAL_STATUS_LABELS[l.new_status]||l.new_status)+(l.changed_by_name?' (av '+l.changed_by_name+')':''), color:DEAL_STATUS_COLORS[l.new_status]||'#94a3b8', icon:'\u2192', fromLog:true});
        });
        timelineItems.sort(function(a,b){ return b.date.localeCompare(a.date); });

        var timelineHtml = timelineItems.map(function(t){
            return '<div style="display:flex;gap:12px;align-items:flex-start;padding:8px 0;border-bottom:1px solid #f1f5f9">'
                +'<div style="min-width:36px;height:36px;border-radius:50%;background:'+t.color+'15;color:'+t.color+';display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;flex-shrink:0">'+t.icon+'</div>'
                +'<div style="flex:1"><div style="font-size:13px;font-weight:600;color:#334155">'+t.label+'</div>'
                +'<div style="font-size:11px;color:#94a3b8">'+t.date+'</div></div></div>';
        }).join('');

        // Section helper
        function secTitle(t){ return '<div style="font-size:12px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin:16px 0 8px;padding-bottom:4px;border-bottom:1px solid #f1f5f9">'+t+'</div>'; }
        function infoRow(label,val,opts){ if(!val) return ''; opts=opts||{}; return '<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 0;font-size:13px"><span style="color:#64748b">'+label+'</span><span style="font-weight:'+(opts.bold?'700':'500')+';color:'+(opts.color||'#334155')+'">'+val+'</span></div>'; }

        // Build the full overlay
        var html = '<div style="position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:10000;display:flex;align-items:flex-start;justify-content:center;padding:20px;overflow-y:auto;backdrop-filter:blur(2px)" onclick="if(event.target===this)this.remove()" id="dealDetailOverlay">'
            +'<div style="background:#fff;border-radius:16px;width:100%;max-width:1100px;box-shadow:0 25px 80px rgba(0,0,0,.25);max-height:calc(100vh - 40px);display:flex;flex-direction:column">'

            // Header
            +'<div style="padding:20px 28px;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:flex-start;flex-shrink:0">'
            +'<div style="flex:1">'
            +'<div style="display:flex;align-items:center;gap:12px;margin-bottom:4px">'
            +'<h2 style="font-size:22px;font-weight:800;color:#1a1a1a;margin:0">'+(d.deal_number||'Ny aff\u00e4r')+'</h2>'+(d.monday_item_id?'<a href="https://solargroup-unit.monday.com/boards/1844756189/pulses/'+d.monday_item_id+'" target="_blank" onclick="event.stopPropagation()" style="display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:8px;background:#6c3faa;color:#fff;font-size:11px;font-weight:600;text-decoration:none;white-space:nowrap" title="\u00d6ppna i Monday.com"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>Monday</a>':'')
            +'<span style="padding:4px 14px;border-radius:20px;font-size:12px;font-weight:600;color:#fff;background:'+(isNeg?'#ef4444':DEAL_STATUS_COLORS[d.status]||'#94a3b8')+'">'+(DEAL_STATUS_LABELS[d.status]||d.status)+'</span>'
            +(d.salj_status?'<span style="padding:3px 10px;border-radius:20px;font-size:11px;font-weight:600;color:#fff;background:'+(SALJ_STATUS_COLORS[d.salj_status]||'#c4c4c4')+'">'+(SALJ_STATUS_LABELS[d.salj_status]||d.salj_status)+'</span>':'')
            +'</div>'
            +'<div style="font-size:14px;color:#64748b">'+(d.customer_name||'')+(d.customer_name2?' & '+d.customer_name2:'')
            +(d.property_address?' \u2014 '+d.property_address:'')+'</div>'
            +'</div>'
            +'<button onclick="document.getElementById(\'dealDetailOverlay\').remove()" style="background:none;border:none;font-size:28px;cursor:pointer;color:#94a3b8;padding:0 4px;line-height:1">&times;</button>'
            +'</div>'

            // Pipeline bar
            +'<div style="padding:16px 28px;border-bottom:1px solid #f1f5f9;flex-shrink:0">'+pipelineHtml+'</div>'

            // Content area - scrollable
            +'<div style="overflow-y:auto;padding:20px 28px 28px;flex:1">'

            // Summary cards row
            +'<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:20px">'
            +(d.ordervarde_ink_moms?'<div style="background:linear-gradient(135deg,#024550,#036b78);border-radius:12px;padding:14px;color:#fff"><div style="font-size:10px;opacity:.7;text-transform:uppercase;letter-spacing:.5px">Orderv\u00e4rde</div><div style="font-size:20px;font-weight:800;margin-top:4px">'+fmtKr(parseFloat(d.ordervarde_ink_moms))+'</div></div>':'')
            +(d.total_marginal_ink_moms?'<div style="background:#f0fdf4;border:1px solid #86efac;border-radius:12px;padding:14px"><div style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.5px">Marginal</div><div style="font-size:20px;font-weight:800;color:#16a34a;margin-top:4px">'+fmtKr(parseFloat(d.total_marginal_ink_moms))+'</div></div>':'')
            +(d.intakter?'<div style="background:#f0fdf4;border:1px solid #86efac;border-radius:12px;padding:14px"><div style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.5px">Int\u00e4kter</div><div style="font-size:20px;font-weight:800;color:#16a34a;margin-top:4px">'+fmtKr(parseFloat(d.intakter))+'</div></div>':'')
            +(d.kostnader?'<div style="background:#fef2f2;border:1px solid #fca5a5;border-radius:12px;padding:14px"><div style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.5px">Kostnader</div><div style="font-size:20px;font-weight:800;color:#ef4444;margin-top:4px">'+fmtKr(parseFloat(d.kostnader))+'</div></div>':'')
            +(d.ue_pris?'<div style="background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:14px"><div style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.5px">UE-pris</div><div style="font-size:20px;font-weight:800;color:#9a3412;margin-top:4px">'+fmtKr(parseFloat(d.ue_pris))+'</div></div>':'')
            +(d.ata?'<div style="background:#fefce8;border:1px solid #fde68a;border-radius:12px;padding:14px"><div style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:.5px">\u00c4TA</div><div style="font-size:20px;font-weight:800;color:#92400e;margin-top:4px">'+fmtKr(parseFloat(d.ata))+'</div></div>':'')
            +'</div>'

            // Three column grid
            +'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:20px">'

            // Column 1: Kund & Fastighet
            +'<div>'
            +secTitle('Kundinformation')
            +'<div style="background:#f8f9fa;border-radius:10px;padding:14px">'
            +'<div style="font-size:15px;font-weight:700;margin-bottom:6px">'+(d.customer_name||'')+'</div>'
            +(d.personnummer?'<div style="font-size:12px;color:#64748b;margin-bottom:6px">Pnr: '+d.personnummer+'</div>':'')
            +(d.customer_name2?'<div style="font-size:14px;font-weight:600;margin-top:8px">'+d.customer_name2+'</div>':'')
            +(d.personnummer2?'<div style="font-size:12px;color:#64748b;margin-bottom:6px">Pnr: '+d.personnummer2+'</div>':'')
            +'<div style="margin-top:8px;display:flex;flex-direction:column;gap:4px">'+contacts+'</div>'
            +'</div>'

            +secTitle('Fastighet')
            +'<div style="background:#f8f9fa;border-radius:10px;padding:14px">'
            +(d.property_address?'<div style="font-size:14px;font-weight:600;margin-bottom:4px">'+d.property_address+'</div>':'<div style="font-size:13px;color:#94a3b8">Ingen fastighet kopplad</div>')
            +(d.property_city?'<div style="font-size:12px;color:#64748b;margin-bottom:6px">'+d.property_city+'</div>':'')
            +infoRow('Beteckning',d.fastighetsbeteckning)
            +infoRow('Huvuds\u00e4kring',d.huvudsakring?d.huvudsakring+' A':null)
            +infoRow('Takmaterial',d.takmaterial)
            +infoRow('Taktyp',d.taktyp)
            +infoRow('N\u00e4t\u00e4gare',d.natagare)
            +infoRow('Anl.id',d.anlaggningsid)
            +'</div>'
            +'</div>'

            // Column 2: Statusar, Team, Produkt
            +'<div>'
            +secTitle('Statusar')
            +'<div style="background:#f8f9fa;border-radius:10px;padding:14px;display:flex;flex-direction:column;gap:6px">'
            +infoRow('Status', DEAL_STATUS_LABELS[d.status]||d.status, {bold:true})
            +infoRow('Status s\u00e4lj', d.salj_status?(SALJ_STATUS_LABELS[d.salj_status]||d.salj_status):null)
            +infoRow('Status order', d.status_order, {bold:true, color:'#1e40af'})
            +(d.avbrott_status?'<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 0"><span style="font-size:13px;color:#64748b">Avbrott</span><span style="font-size:11px;padding:3px 12px;border-radius:10px;color:#fff;background:#ef4444;font-weight:600">'+d.avbrott_status+'</span></div>':'')
            +(d.ansvarig_avbrott?infoRow('Ansvarig avbrott',d.ansvarig_avbrott):'')
            +(d.datum_avbrott?infoRow('Datum avbrott',d.datum_avbrott,{color:'#ef4444'}):'')
            +infoRow('AP Status', d.ap_status)
            +infoRow('AP Status 2', d.ap_status_2)
            +infoRow('Region', d.region, {bold:true, color:'#1e40af'})
            +infoRow('F\u00f6ranm\u00e4lan', d.foranmalan)
            +'</div>'

            +secTitle('S\u00e4ljare & Team')
            +'<div style="background:#f8f9fa;border-radius:10px;padding:14px;display:flex;flex-direction:column;gap:4px">'
            +infoRow('S\u00e4ljare 1', d.saljare1_name, {bold:true})
            +infoRow('S\u00e4ljare 2', d.saljare2_name)
            +infoRow('Bokare', d.bokare_name)
            +infoRow('Projekt\u00f6r', d.projektor_name)
            +infoRow('Best\u00e4llare', d.bestallare_name)
            +infoRow('M\u00f6tesbokare', d.motesbokare_name)
            +'</div>'

            +(products?secTitle('Produkter & Teknik')
            +'<div style="background:#f8f9fa;border-radius:10px;padding:14px;display:flex;flex-direction:column;gap:4px">'
            +infoRow('Produkter', products, {bold:true})
            +infoRow('Vad \u00e4r s\u00e5lt', d.vad_ar_salt)
            +infoRow('Antal solceller', d.antal_solceller)
            +infoRow('M\u00e4rke solceller', d.marke_solceller)
            +infoRow('Batteri storlek', d.batteri_storlek)
            +infoRow('M\u00e4rke batteri', d.marke_batteri)
            +infoRow('Projektnummer', d.projektnummer)
            +'</div>':'')
            +'</div>'

            // Column 3: Ekonomi, UE, Timeline
            +'<div>'
            +secTitle('Ekonomi')
            +'<div style="background:#f0fdf4;border:1px solid #86efac;border-radius:10px;padding:14px;display:flex;flex-direction:column;gap:4px">'
            +infoRow('Orderv\u00e4rde', d.ordervarde_ink_moms?fmtKr(parseFloat(d.ordervarde_ink_moms)):null, {bold:true, color:'#024550'})
            +infoRow('Marginal', d.total_marginal_ink_moms?fmtKr(parseFloat(d.total_marginal_ink_moms)):null, {color:'#16a34a'})
            +infoRow('Int\u00e4kter', d.intakter?fmtKr(parseFloat(d.intakter)):null, {bold:true, color:'#16a34a'})
            +infoRow('Kostnader', d.kostnader?fmtKr(parseFloat(d.kostnader)):null, {color:'#ef4444'})
            +infoRow('UE-pris', d.ue_pris?fmtKr(parseFloat(d.ue_pris)):null, {color:'#9a3412'})
            +infoRow('\u00c4TA', d.ata?fmtKr(parseFloat(d.ata)):null, {color:'#92400e'})
            +infoRow('GT', d.gt_belopp?fmtKr(parseFloat(d.gt_belopp)):null)
            +infoRow('ROT', d.rot_belopp?fmtKr(parseFloat(d.rot_belopp)):null)
            +infoRow('Finans via oss', d.finans_via_oss?fmtKr(parseFloat(d.finans_via_oss)):null)
            +infoRow('Faktura', d.skapa_faktura)
            +infoRow('Fakturastatus', d.faktura_status)
            +infoRow('Betvillkor', d.betvillkor)
            +'</div>'

            +(d.tilldelad_ue?secTitle('Entrepren\u00f6r (UE)')
            +'<div style="background:#fff7ed;border:1px solid #fed7aa;border-radius:10px;padding:14px">'
            +'<div style="font-size:14px;font-weight:700;color:#9a3412;margin-bottom:4px">'+d.tilldelad_ue+'</div>'
            +infoRow('UE-pris', d.ue_pris?fmtKr(parseFloat(d.ue_pris)):null, {color:'#9a3412',bold:true})
            +'</div>':'')

            +secTitle('Aktivitetslogg')
            +'<div style="background:#f8f9fa;border-radius:10px;padding:14px;max-height:300px;overflow-y:auto">'
            +(timelineHtml||'<div style="font-size:13px;color:#94a3b8;text-align:center;padding:16px 0">Ingen aktivitet loggad</div>')
            +'</div>'

            // Datum
            +secTitle('Viktiga datum')
            +'<div style="background:#f8f9fa;border-radius:10px;padding:14px;display:flex;flex-direction:column;gap:4px">'
            +infoRow('Datum s\u00e4lj', d.datum_salj, {bold:true})
            +infoRow('Projektering bokad', d.projektering_bokad)
            +infoRow('F\u00e4rdigst\u00e4llt', d.datum_fardigstall, {color:'#10b981',bold:true})
            +infoRow('Avbrott', d.datum_avbrott, {color:'#ef4444'})
            +'</div>'
            +'</div>'

            +'</div>' // end grid

            // Status change buttons
            +'<div style="margin-top:20px;padding-top:16px;border-top:1px solid #e5e7eb">'
            +'<div style="font-size:12px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">\u00c4ndra status</div>'
            +'<div style="display:flex;gap:6px;flex-wrap:wrap">'
            +stages.concat(['anger','ej_godkand','avbrott']).map(function(s){ return '<button onclick="changeDealStatus('+d.id+',&quot;'+s+'&quot;)" style="padding:5px 12px;border-radius:8px;font-size:11px;font-weight:600;border:2px solid '+(d.status===s?DEAL_STATUS_COLORS[s]:'#e5e7eb')+';background:'+(d.status===s?DEAL_STATUS_COLORS[s]:'#fff')+';color:'+(d.status===s?'#fff':'#64748b')+';cursor:pointer;transition:all .15s" onmouseover="this.style.borderColor=\''+DEAL_STATUS_COLORS[s]+'\'" onmouseout="this.style.borderColor=\''+(d.status===s?DEAL_STATUS_COLORS[s]:'#e5e7eb')+'\'">'+(DEAL_STATUS_LABELS[s]||s)+'</button>'; }).join('')
            +'</div></div>'

            +'</div>' // end content area
            +'</div></div>'; // end overlay

        document.body.insertAdjacentHTML('beforeend', html);
    } catch(e) { console.error('showDealDetail error:', e); }
}

async function changeDealStatus(dealId, newStatus) {
    const user = JSON.parse(localStorage.getItem('solarUser')||'{}');
    try {
        const r = await fetch('api/deals.php?action=status', {
            method: 'POST',
            headers: {'Content-Type':'application/json'},
            body: JSON.stringify({ deal_id: dealId, status: newStatus, staff_id: user.id||0 })
        });
        const data = await r.json();
        if (data.success) {
            const overlay = document.getElementById('dealDetailOverlay');
            if (overlay) overlay.remove();
            showDealDetail(dealId);
            loadDeals();
        }
    } catch(e) { console.error('changeDealStatus error:', e); }
}