backups/2026-03-27/dashboard.js.bak

Code: DEV-A0EF9C26 Size: 6.1 KB Lines: 107 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/backups/2026-03-27/dashboard.js.bak

Task / Comment

Open report form
// dashboard.js - Dashboard + charts

async function loadDashboard() {
    try {
        // Load deal stats
        const [statsR, dealsR, custR] = await Promise.all([
            fetch('api/deals.php?action=stats&t='+Date.now()),
            fetch('api/deals.php?limit=5&status_exclude=anger,ej_godkand,avbrott&t='+Date.now()),
            fetch('api/customers.php?limit=1&t='+Date.now())
        ]);
        const stats = await statsR.json();
        const dealsData = await dealsR.json();
        const custData = await custR.json();

        const el = (id,v) => { const e=document.getElementById(id); if(e) e.textContent=v; };

        // Total value (all active deals)
        let totalVal = 0, offertCount = 0, acceptedCount = 0;
        Object.entries(stats).forEach(([k,v]) => {
            totalVal += v.value || 0;
            if (k === 'offert') offertCount = v.count || 0;
            if (['order','projektering','bestallning','leverans','montering','besiktning','fardigstall'].includes(k)) acceptedCount += v.count || 0;
        });

        el('dashTotalValue', fmtKr(totalVal));
        el('dashOffertar', offertCount);
        el('dashKunder', custData.total || 0);
        el('dashAccepted', acceptedCount);

        // Recent deals
        const container = document.getElementById('dashRecentDeals');
        if (container && dealsData.deals) {
            if (!dealsData.deals.length) {
                container.innerHTML = '<div style="padding:20px;text-align:center;color:#94a3b8;font-size:13px">Inga affärer ännu</div>';
            } else {
                container.innerHTML = dealsData.deals.map(d => {
                    const products = (d.product_types||'').split(',').filter(Boolean).map(p => PRODUCT_LABELS[p]||p).join(', ');
                    const color = DEAL_STATUS_COLORS[d.status]||'#94a3b8';
                    const val = d.ordervarde_ink_moms ? Math.round(parseFloat(d.ordervarde_ink_moms)).toLocaleString('sv-SE')+' kr' : '-';
                    return '<div class="offert-item" style="cursor:pointer" onclick="showPage(\'projekt\');setTimeout(()=>showDealDetail('+d.id+'),200)">'
                        +'<div class="offert-item-left"><h4>'+d.customer_name+'</h4><p>'+(products||d.deal_number)+' &bull; '+(d.datum_salj||'')+'</p></div>'
                        +'<div class="offert-item-right"><div class="offert-price">'+val+'</div><div class="offert-status" style="color:'+color+'">'+DEAL_STATUS_LABELS[d.status]+'</div></div>'
                        +'</div>';
                }).join('');
            }
        }
        // Top sellers
        const tsContainer = document.getElementById('dashTopSellers');
        if (tsContainer) {
            try {
                const tsR = await fetch('api/deals.php?action=top_sellers&t='+Date.now());
                const sellers = await tsR.json();
                if (sellers.length) {
                    const medals = ['medal-gold','medal-silver','medal-bronze'];
                    tsContainer.innerHTML = sellers.slice(0,5).map((s,i) =>
                        '<div class="top-seller-item">'
                        +(i<3?'<div class="top-seller-medal '+medals[i]+'">'+(i+1)+'</div>':'<div style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#94a3b8">'+(i+1)+'</div>')
                        +'<div class="top-seller-info"><div class="top-seller-name">'+s.name+'</div><div class="top-seller-detail">'+s.deal_count+' order</div></div>'
                        +'<div class="top-seller-amount">'+fmtKr(s.total_value)+'</div>'
                        +'</div>'
                    ).join('');
                } else {
                    tsContainer.innerHTML = '<div style="padding:20px;text-align:center;color:#94a3b8;font-size:13px">Ingen data</div>';
                }
            } catch(e) { tsContainer.innerHTML = '<div style="padding:20px;text-align:center;color:#94a3b8;font-size:13px">-</div>'; }
        }
    } catch(e) { console.error('loadDashboard error:', e); }
}

function fmtKr(n){return n?Math.round(n).toLocaleString('sv-SE')+' kr':'-'}

/* === KUNDER (DataTable) === */
let customerDT = null;
let customersLoaded = false;
let allCustData = [];


/* === DASHBOARD CHARTS === */
function initDashCharts(){
    const ctxM=document.getElementById('chartMonthly');
    const ctxY=document.getElementById('chartYearly');
    if(!ctxM||!ctxY)return;
    new Chart(ctxM,{
        type:'bar',
        data:{
            labels:['V6','V7','V8','V9'],
            datasets:[
                {label:'Order',data:[185000,92500,248000,134200],backgroundColor:'#10b981',borderRadius:4},
                {label:'Offerter',data:[87200,53400,96800,42500],backgroundColor:'#eab308',borderRadius:4},
                {label:'Förlorade',data:[0,156000,0,73200],backgroundColor:'#ef4444',borderRadius:4}
            ]
        },
        options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{usePointStyle:true,padding:12,font:{family:'Inter',size:11}}}},scales:{y:{beginAtZero:true,ticks:{callback:v=>v>=1000?(v/1000)+'k':''+v,font:{family:'Inter',size:11}},grid:{color:'#f1f5f9'}},x:{grid:{display:false},ticks:{font:{family:'Inter',size:11}}}}}
    });
    new Chart(ctxY,{
        type:'line',
        data:{
            labels:['Jan','Feb','Mar','Apr','Maj','Jun','Jul','Aug','Sep','Okt','Nov','Dec'],
            datasets:[
                {label:'Försäljning',data:[420000,659700,null,null,null,null,null,null,null,null,null,null],borderColor:'#024550',backgroundColor:'rgba(2,69,80,.08)',fill:true,tension:.3,pointRadius:5,pointBackgroundColor:'#024550'},
                {label:'Mål',data:[500000,500000,550000,550000,600000,650000,400000,450000,600000,650000,700000,750000],borderColor:'#e5e7eb',borderDash:[5,5],fill:false,tension:.3,pointRadius:0}
            ]
        },
        options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{usePointStyle:true,padding:12,font:{family:'Inter',size:11}}}},scales:{y:{beginAtZero:true,ticks:{callback:v=>v>=1000?(v/1000)+'k':''+v,font:{family:'Inter',size:11}},grid:{color:'#f1f5f9'}},x:{grid:{display:false},ticks:{font:{family:'Inter',size:11}}}}}
    });
}