// dashboard.js - Dashboard + charts
function _dashScopeParam(widgetKey){
if(typeof widgetScope !== 'function') return '';
const s = widgetScope(widgetKey);
return (s === 'own' && typeof gStaffId !== 'undefined' && gStaffId) ? '&saljare_id='+gStaffId : '';
}
async function loadDashboard() {
try {
const monthNames = ['Januari','Februari','Mars','April','Maj','Juni','Juli','Augusti','September','Oktober','November','December'];
const currentMonth = monthNames[new Date().getMonth()];
// Set dashboard title with current month
const dashTitle = document.getElementById('dashTitle');
if (dashTitle) dashTitle.textContent = 'Dashboard - ' + currentMonth;
// Apply per-widget visibility
const _vis = (k) => typeof isWidgetVisible==='function' ? isWidgetVisible(k) : true;
[['dashTotalValue','stat_total'],['dashOffertar','stat_offertar'],['dashKunder','stat_kunder'],['dashAccepted','stat_accepted']].forEach(([id,key]) => {
const card = document.getElementById(id)?.closest('.stat-card');
if(card) card.style.display = _vis(key) ? '' : 'none';
});
[['dashTopSellers','top_sellers','.dash-panel'],['dashRecentDeals','recent_deals','.dash-panel'],['dashMeetings','meetings','.dash-panel'],['chartMonthly','chart_monthly','.dash-chart-panel'],['chartYearly','chart_yearly','.dash-chart-panel']].forEach(([id,key,sel]) => {
const p = document.getElementById(id)?.closest(sel);
if(p) p.style.display = _vis(key) ? '' : 'none';
});
// Load deal stats for current month
const now = new Date();
const dashMonth = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0');
const statsScope = _dashScopeParam('stat_total');
const dealsScope = _dashScopeParam('recent_deals');
const custScope = _dashScopeParam('stat_kunder');
const [statsR, dealsR, custR] = await Promise.all([
fetch('api/deals.php?action=stats&month='+dashMonth+statsScope+'&t='+Date.now()),
fetch('api/deals.php?limit=5&status_exclude=anger,ej_godkand,avbrott&sort=datum_salj'+dealsScope+'&t='+Date.now()),
fetch('api/customers.php?limit=1'+custScope+'&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 (exclude anger/ej_godkand/avbrott)
const excludeStatuses = ['anger','ej_godkand','avbrott'];
let totalVal = 0, offertCount = 0, acceptedCount = 0;
Object.entries(stats).forEach(([k,v]) => {
if (!excludeStatuses.includes(k)) 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)+' • '+(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 now = new Date();
const curMonth = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0');
const tsR = await fetch('api/deals.php?action=top_sellers&month='+curMonth+'&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 === */
let dashMonthlyChart = null;
let dashYearlyChart = null;
async function initDashCharts(){
const ctxM=document.getElementById('chartMonthly');
const ctxY=document.getElementById('chartYearly');
if(!ctxM||!ctxY)return;
try {
const monthlyScope = _dashScopeParam('chart_monthly');
const yearlyScope = _dashScopeParam('chart_yearly');
const [monthlyR, yearlyR] = await Promise.all([
fetch('api/deals.php?action=chart_monthly'+monthlyScope+'&t='+Date.now()),
fetch('api/deals.php?action=chart_yearly'+yearlyScope+'&t='+Date.now())
]);
const monthlyData = await monthlyR.json();
const yearlyData = await yearlyR.json();
// Update month title
const monthTitle = document.getElementById('dashMonthlyTitle');
if (monthTitle) monthTitle.textContent = 'Försäljning ' + monthlyData.month_name;
// Monthly chart — per week
const weekLabels = monthlyData.weeks.map(w => w.week_label);
const orderData = monthlyData.weeks.map(w => parseFloat(w.order_value));
const offertData = monthlyData.weeks.map(w => parseFloat(w.offert_value));
const lostData = monthlyData.weeks.map(w => parseFloat(w.lost_value));
if (dashMonthlyChart) dashMonthlyChart.destroy();
dashMonthlyChart = new Chart(ctxM,{
type:'bar',
data:{
labels: weekLabels,
datasets:[
{label:'Order',data:orderData,backgroundColor:'#10b981',borderRadius:4},
{label:'Offerter',data:offertData,backgroundColor:'#eab308',borderRadius:4},
{label:'Förlorade',data:lostData,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}}}}}
});
// Yearly chart
const monthLabels = ['Jan','Feb','Mar','Apr','Maj','Jun','Jul','Aug','Sep','Okt','Nov','Dec'];
const salesData = yearlyData.months;
// Update year title
const yearTitle = document.getElementById('dashYearlyTitle');
if (yearTitle) yearTitle.textContent = 'Helårsöversikt ' + yearlyData.year;
if (dashYearlyChart) dashYearlyChart.destroy();
dashYearlyChart = new Chart(ctxY,{
type:'line',
data:{
labels: monthLabels,
datasets:[
{label:'Försäljning',data:salesData,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}}}}}
});
} catch(e) {
console.error('initDashCharts error:', e);
}
}