js/admin.js.bak_20260428_200409_impperm

Code: DEV-E3B3D04C Size: 26.1 KB Lines: 494 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/js/admin.js.bak_20260428_200409_impperm

Task / Comment

Open report form
// admin.js - Admin, permissions, pending users

function updateAdminVisibility(){
    const navAdmin = document.getElementById('navAdmin');
    if(navAdmin){
        navAdmin.style.display = (gUserRole === 'systemadmin' || gUserRole === 'admin' || gUserRole === 'saljchef') ? '' : 'none';
    }
}

// Admin-sida
function switchAdminTab(tab){
    document.querySelectorAll('[data-admin-tab]').forEach(t => {
        const isActive = t.dataset.adminTab === tab;
        t.style.borderBottomColor = isActive ? '#024550' : 'transparent';
        t.style.color = isActive ? '#024550' : '#64748b';
    });
    document.getElementById('adminUsersPanel').style.display = tab==='users' ? '' : 'none';
    document.getElementById('adminPendingPanel').style.display = tab==='pending' ? '' : 'none';
    document.getElementById('adminPermissionsPanel').style.display = tab==='permissions' ? '' : 'none';
    if(tab==='users') loadAdminUsers();
    if(tab==='pending') loadAdminPending();
    if(tab==='permissions') loadPermissions();
}

const PAGE_LABELS = {oversikt:'Dashboard',faltsalj:'FältSälj',leads:'Leads',kunder:'Kunder',produkter:'Produktkatalog',konfigurator:'Kalkyler',projekt:'Affärer',projektering:'Projektflöde',ue:'Entreprenörer',inkop:'Inköp',leverantorer:'Leverantörer',ekonomi:'Ekonomi',bildgen:'Bildgenerering',personal:'Personal',kalender:'Kalender',inkorg:'Inkorg',dagrapport:'Dagrapport','my-salary':'My Salary',profile:'Profile',settings:'Inställningar'};
const PAGE_KEYS = Object.keys(PAGE_LABELS);
const PERM_ROLES = ['admin','saljchef','saljare','installator','projektledare','ekonomi'];
const PAGE_SETTINGS = {
    oversikt: {
        label: 'Widgets',
        items: [
            { label: 'Totalt Värde', visibleKey: 'dashboard.widget.stat_total.visible', scopeKey: 'dashboard.widget.stat_total.scope' },
            { label: 'Pågående Offerter', visibleKey: 'dashboard.widget.stat_offertar.visible', scopeKey: 'dashboard.widget.stat_offertar.scope' },
            { label: 'Kunder', visibleKey: 'dashboard.widget.stat_kunder.visible', scopeKey: 'dashboard.widget.stat_kunder.scope' },
            { label: 'Accepterade', visibleKey: 'dashboard.widget.stat_accepted.visible', scopeKey: 'dashboard.widget.stat_accepted.scope' },
            { label: 'Top Säljare', visibleKey: 'dashboard.widget.top_sellers.visible' },
            { label: 'Senaste Affärer', visibleKey: 'dashboard.widget.recent_deals.visible', scopeKey: 'dashboard.widget.recent_deals.scope' },
            { label: 'Kommande Möten', visibleKey: 'dashboard.widget.meetings.visible' },
            { label: 'Försäljning (månad)', visibleKey: 'dashboard.widget.chart_monthly.visible', scopeKey: 'dashboard.widget.chart_monthly.scope' },
            { label: 'Helårsöversikt', visibleKey: 'dashboard.widget.chart_yearly.visible', scopeKey: 'dashboard.widget.chart_yearly.scope' },
        ]
    },
    kunder: { label: 'Inställningar', items: [{ label: 'Visa kunder', scopeKey: 'kunder.scope' }] },
    faltsalj: { label: 'Inställningar', items: [{ label: 'Visa rutter', scopeKey: 'faltsalj.routes.scope' }] },
    leads: { label: 'Inställningar', items: [{ label: 'Visa leads', scopeKey: 'leads.scope' }] },
    projekt: { label: 'Inställningar', items: [{ label: 'Visa affärer', scopeKey: 'projekt.scope' }] },
    konfigurator: { label: 'Inställningar', items: [
        { label: 'Visa kalkyler', scopeKey: 'kalkyler.scope' },
        { label: 'Visa priser', toggleKey: 'kalkyler.show_prices' },
        { label: 'Visa CalcBuilder', toggleKey: 'kalkyler.show_calcbuilder' }
    ]},
    produkter: { label: 'Inställningar', items: [{ label: 'Visa priser', toggleKey: 'produkter.show_prices' }] }
};
function permDefaultScope(role){
    return ['admin','systemadmin','saljchef','ekonomi'].indexOf(role) >= 0 ? 'all' : 'own';
}
function permDefaultToggle(role, key){
    // Per-key defaults för speciella toggles. Annars är default '1' (på).
    if(key === 'personal.impersonate'){
        return ['admin','systemadmin'].indexOf(role) >= 0 ? '1' : '0';
    }
    return '1';
}
function permGetSetting(role, key){
    const r = permSettings[role] || {};
    if(r[key] !== undefined) return r[key];
    if(key.endsWith('.scope')) return permDefaultScope(role);
    return permDefaultToggle(role, key);
}
let permData = {};
let permSettings = {};
let permExpandedPages = {};
let permSelectedRole = 'saljare';

var menuOrder = [];

async function loadPermissions(){
    try {
        const [permRes, settRes] = await Promise.all([
            fetch('/api/permissions.php'),
            fetch('/api/permissions.php?action=settings')
        ]);
        permData = await permRes.json();
        permSettings = await settRes.json();
        renderPermRoleTabs();
        renderPermMatrix();
        await loadMenuOrder();
    } catch(e){ console.error('loadPermissions error', e); }
}

async function loadMenuOrder(){
    try {
        const res = await fetch('/api/permissions.php?action=order&t='+Date.now());
        menuOrder = await res.json();
        renderMenuOrder();
    } catch(e){ console.error('loadMenuOrder error', e); }
}

function renderMenuOrder(){
    const container = document.getElementById('menuOrderList');
    if(!container) return;
    container.innerHTML = menuOrder.map((key, i) => {
        const label = PAGE_LABELS[key] || key;
        return `<div draggable="true" data-page-key="${key}" style="padding:8px 12px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;color:#1a1a1a;cursor:grab;display:flex;align-items:center;gap:8px;user-select:none;transition:box-shadow .15s" onmousedown="this.style.cursor='grabbing'" onmouseup="this.style.cursor='grab'"><svg viewBox="0 0 24 24" style="width:14px;height:14px;stroke:#94a3b8;fill:none;stroke-width:2;flex-shrink:0"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>${label}</div>`;
    }).join('');
    // Add drag events
    const items = container.querySelectorAll('[draggable]');
    items.forEach(item => {
        item.addEventListener('dragstart', e => {
            e.dataTransfer.setData('text/plain', item.dataset.pageKey);
            item.style.opacity = '0.4';
        });
        item.addEventListener('dragend', e => { item.style.opacity = '1'; });
        item.addEventListener('dragover', e => {
            e.preventDefault();
            item.style.boxShadow = '0 -2px 0 #024550';
        });
        item.addEventListener('dragleave', e => { item.style.boxShadow = ''; });
        item.addEventListener('drop', async e => {
            e.preventDefault();
            item.style.boxShadow = '';
            const draggedKey = e.dataTransfer.getData('text/plain');
            const targetKey = item.dataset.pageKey;
            if(draggedKey === targetKey) return;
            const oldIdx = menuOrder.indexOf(draggedKey);
            const newIdx = menuOrder.indexOf(targetKey);
            menuOrder.splice(oldIdx, 1);
            menuOrder.splice(newIdx, 0, draggedKey);
            renderMenuOrder();
            await saveMenuOrder();
            applyNavOrder();
        });
    });
}

async function saveMenuOrder(){
    try {
        await fetch('/api/permissions.php?action=order', {
            method:'POST', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({ order: menuOrder })
        });
    } catch(e){ console.error('saveMenuOrder error', e); }
}

function applyNavOrder(){
    const nav = document.querySelector('.sidebar-nav');
    if(!nav) return;
    const items = Array.from(nav.querySelectorAll('.nav-item[data-page]'));
    const adminItem = items.find(el => el.dataset.page === 'admin');
    items.sort((a, b) => {
        const ai = menuOrder.indexOf(a.dataset.page);
        const bi = menuOrder.indexOf(b.dataset.page);
        return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
    });
    items.forEach(item => nav.appendChild(item));
    if(adminItem) nav.appendChild(adminItem);
}

function renderPermRoleTabs(){
    const container = document.getElementById('permRoleTabs');
    container.innerHTML = PERM_ROLES.map(r => {
        const active = r === permSelectedRole;
        return `<button onclick="selectPermRole('${r}')" style="padding:8px 16px;border-radius:8px;font-size:13px;font-weight:${active?'600':'500'};cursor:pointer;font-family:inherit;border:1.5px solid ${active?'#024550':'#e5e7eb'};background:${active?'#024550':'#fff'};color:${active?'#fff':'#334155'};transition:all .15s">${ROLE_LABELS[r]}</button>`;
    }).join('');
}

function selectPermRole(role){
    permSelectedRole = role;
    renderPermRoleTabs();
    renderPermMatrix();
}

function _permToggleHtml(checked, onchange){
    return `<label style="position:relative;display:inline-block;width:44px;height:24px;cursor:pointer;flex-shrink:0">`
        +`<input type="checkbox" ${checked?'checked':''} onchange="${onchange}" style="opacity:0;width:0;height:0">`
        +`<span style="position:absolute;inset:0;background:${checked?'#10b981':'#e5e7eb'};border-radius:24px;transition:all .2s"></span>`
        +`<span style="position:absolute;left:${checked?'22px':'2px'};top:2px;width:20px;height:20px;background:#fff;border-radius:50%;transition:all .2s;box-shadow:0 1px 3px rgba(0,0,0,.15)"></span>`
        +`</label>`;
}
function _permScopeSelect(currentValue, onchange){
    return `<select onchange="${onchange}" style="padding:5px 10px;border:1.5px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;background:#fff;cursor:pointer">`
        +`<option value="own"${currentValue==='own'?' selected':''}>Egna</option>`
        +`<option value="all"${currentValue==='all'?' selected':''}>Allas</option>`
        +`</select>`;
}
function renderPermMatrix(){
    const container = document.getElementById('permMatrixContainer');
    const rolePerms = permData[permSelectedRole] || {};
    let html = '<div style="border:1px solid #e5e7eb;border-radius:10px;overflow:hidden">';
    html += '<div style="display:grid;grid-template-columns:1fr auto;padding:10px 16px;background:#f8fafc;border-bottom:1px solid #e5e7eb"><div style="font-weight:600;font-size:13px;color:#64748b">Menyval</div><div style="font-weight:600;font-size:13px;color:#64748b;text-align:center">Synlig</div></div>';
    PAGE_KEYS.forEach((key, i) => {
        const visible = rolePerms[key] !== undefined ? rolePerms[key] : 1;
        const bg = i % 2 === 0 ? '#fff' : '#fafbfc';
        const border = i < PAGE_KEYS.length - 1 ? 'border-bottom:1px solid #f1f5f9;' : '';
        const hasSettings = !!PAGE_SETTINGS[key];
        const expanded = !!permExpandedPages[key];
        const chevron = hasSettings
            ? `<button onclick="togglePermPageExpand('${key}')" style="background:none;border:none;cursor:pointer;padding:2px;color:#64748b;display:flex;align-items:center;transform:rotate(${expanded?90:0}deg);transition:transform .15s"><svg viewBox="0 0 24 24" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2"><polyline points="9 18 15 12 9 6"/></svg></button>`
            : '<span style="width:18px;display:inline-block"></span>';
        html += `<div style="display:grid;grid-template-columns:1fr auto;${border}background:${bg}">`;
        html += `<div style="padding:10px 16px;font-size:14px;color:#1a1a1a;display:flex;align-items:center;gap:8px">${chevron}<span>${PAGE_LABELS[key]}</span></div>`;
        html += `<div style="padding:10px 16px;text-align:center">${_permToggleHtml(!!visible, `togglePerm('${key}',this.checked)`)}</div>`;
        html += `</div>`;
        if(hasSettings && expanded){
            html += renderPermSettingsPanel(key, bg, border);
        }
    });
    html += '</div>';
    container.innerHTML = html;
}
function renderPermSettingsPanel(pageKey, bg, border){
    const cfg = PAGE_SETTINGS[pageKey];
    let html = `<div style="${border}background:${bg};padding:8px 16px 14px 42px">`;
    html += `<div style="font-size:11px;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px">${cfg.label}</div>`;
    html += `<div style="display:flex;flex-direction:column;gap:6px">`;
    cfg.items.forEach(item => {
        html += `<div style="display:flex;align-items:center;gap:12px;padding:6px 10px;background:#fff;border:1px solid #f1f5f9;border-radius:8px">`;
        html += `<div style="flex:1;font-size:13px;color:#334155">${item.label}</div>`;
        if(item.visibleKey){
            const v = permGetSetting(permSelectedRole, item.visibleKey) === '1';
            html += `<div style="display:flex;align-items:center;gap:6px"><span style="font-size:11px;color:#94a3b8">Visa</span>${_permToggleHtml(v, `togglePermSetting('${item.visibleKey}',this.checked?'1':'0')`)}</div>`;
        }
        if(item.scopeKey){
            const s = permGetSetting(permSelectedRole, item.scopeKey);
            html += `<div style="display:flex;align-items:center;gap:6px"><span style="font-size:11px;color:#94a3b8">Data</span>${_permScopeSelect(s, `togglePermSetting('${item.scopeKey}',this.value)`)}</div>`;
        }
        if(item.toggleKey){
            const t = permGetSetting(permSelectedRole, item.toggleKey) === '1';
            html += `<div>${_permToggleHtml(t, `togglePermSetting('${item.toggleKey}',this.checked?'1':'0')`)}</div>`;
        }
        html += `</div>`;
    });
    html += `</div></div>`;
    return html;
}
function togglePermPageExpand(pageKey){
    permExpandedPages[pageKey] = !permExpandedPages[pageKey];
    renderPermMatrix();
}
async function togglePermSetting(key, value){
    if(!permSettings[permSelectedRole]) permSettings[permSelectedRole] = {};
    permSettings[permSelectedRole][key] = String(value);
    renderPermMatrix();
    try {
        await fetch('/api/permissions.php?action=settings', {
            method:'POST', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({ role: permSelectedRole, settings: { [key]: String(value) } })
        });
        if(permSelectedRole === gUserRole && typeof applyRoleSettings === 'function') applyRoleSettings();
    } catch(e){ console.error('togglePermSetting error', e); }
}

async function togglePerm(pageKey, visible){
    if(!permData[permSelectedRole]) permData[permSelectedRole] = {};
    permData[permSelectedRole][pageKey] = visible ? 1 : 0;
    renderPermMatrix();
    try {
        await fetch('/api/permissions.php', {
            method:'POST', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({ role: permSelectedRole, permissions: { [pageKey]: visible ? 1 : 0 } })
        });
    } catch(e){ console.error('togglePerm error', e); }
    if(permSelectedRole === gUserRole) applyNavPermissions(); if(typeof loadDashboard==="function") loadDashboard();
}

async function applyNavPermissions(){
    try {
        const res = await fetch('/api/permissions.php?role=' + encodeURIComponent(gUserRole || 'saljare') + '&t=' + Date.now());
        const data = await res.json();
        const perms = data.permissions || data;
        const order = data.order || [];
        document.querySelectorAll('.nav-item[data-page]').forEach(el => {
            const page = el.dataset.page;
            if(page === 'admin') return;
            if(perms[page] !== undefined) {
                el.style.display = perms[page] ? '' : 'none';
            } else {
                el.style.display = '';
            }
        });
        if(order.length > 0) {
            menuOrder = order;
            applyNavOrder();
        }
    } catch(e){ console.error('applyNavPermissions error', e); }
}

const ROLE_LABELS = {systemadmin:'Systemadmin', admin:'Admin', saljchef:'Säljchef', saljare:'Säljare', installator:'Installatör', projektledare:'Projektledare', ekonomi:'Ekonomi', pending:'Väntande'};
const ROLE_OPTIONS = ['systemadmin','admin','saljchef','saljare','installator','projektledare','ekonomi'];

let allAdminUsers = [];
async function loadAdminUsers(){
    try {
        const res = await fetch('/api/staff.php?active=0&t='+Date.now());
        const users = await res.json();
        allAdminUsers = users.filter(u => u.approved == 1);
        filterAdminUsers();
    } catch(err){
        console.error('loadAdminUsers error:', err);
    }
}

function filterAdminUsers(){
    const search = (document.getElementById('adminUserSearch')?.value || '').toLowerCase();
    const roleFilter = document.getElementById('adminUserRoleFilter')?.value || '';
    let filtered = allAdminUsers;
    if(search) filtered = filtered.filter(u => (u.name||'').toLowerCase().includes(search) || (u.email||'').toLowerCase().includes(search));
    if(roleFilter) filtered = filtered.filter(u => u.role === roleFilter);
    const tbody = document.getElementById('adminUsersBody');
    document.getElementById('adminUserCount').textContent = filtered.length + ' av ' + allAdminUsers.length + ' användare';
    tbody.innerHTML = filtered.map(u => {
            const roleOpts = ROLE_OPTIONS.filter(r => r !== 'systemadmin' || gUserRole === 'systemadmin').map(r => '<option value="'+r+'"'+(u.role===r?' selected':'')+'>'+ROLE_LABELS[r]+'</option>').join('');
            const lastLogin = u.last_login ? new Date(u.last_login).toLocaleDateString('sv-SE') : '—';
            return '<tr style="border-bottom:1px solid #f1f5f9">'
                +'<td style="padding:10px 12px;font-weight:500">'+escHtml(u.name)+'</td>'
                +'<td style="padding:10px 12px;color:#64748b">'+escHtml(u.email)+'</td>'
                +'<td style="padding:10px 12px"><select onchange="changeUserRole('+u.id+',this.value)" style="padding:4px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;background:#fff;cursor:pointer">'+roleOpts+'</select></td>'
                +'<td style="padding:10px 12px;color:#64748b;font-size:12px">'+(u.phone||'—')+'</td>'
                +'<td style="padding:10px 12px;text-align:center"><button onclick="toggleUserActive('+u.id+','+(u.active?0:1)+')" style="width:36px;height:20px;border-radius:10px;border:none;cursor:pointer;background:'+(u.active==1?'#059669':'#cbd5e1')+';position:relative;transition:background .2s"><span style="position:absolute;top:2px;'+(u.active==1?'right:2px':'left:2px')+';width:16px;height:16px;background:#fff;border-radius:50%;transition:all .2s"></span></button></td>'
                +'<td style="padding:10px 12px;color:#94a3b8;font-size:12px">'+lastLogin+'</td>'
                +(gUserRole==='systemadmin'?'<td style="padding:10px 12px;text-align:center"><button onclick="deleteUser('+u.id+',\''+escHtml(u.name)+'\')" style="background:none;border:none;cursor:pointer;color:#ef4444;font-size:14px" title="Ta bort">&times;</button></td>':'<td></td>')
                +'</tr>';
        }).join('');
}

async function loadAdminPending(){
    try {
        const res = await fetch('/api/staff.php?approved=0&t='+Date.now());
        const pending = await res.json();
        const listEl = document.getElementById('adminPendingList');
        const emptyEl = document.getElementById('adminPendingEmpty');
        const countEl = document.getElementById('adminPendingCount');
        const badgeEl = document.getElementById('adminPendingBadge');

        if(pending.length === 0){
            emptyEl.style.display='block';
            listEl.style.display='none';
            if(countEl) countEl.style.display='none';
            if(badgeEl) badgeEl.style.display='none';
            return;
        }

        emptyEl.style.display='none';
        listEl.style.display='flex';
        if(countEl){ countEl.textContent=pending.length; countEl.style.display='inline'; }
        if(badgeEl){ badgeEl.textContent=pending.length; badgeEl.style.display='inline'; }

        listEl.innerHTML = pending.map(u => {
            const createdD = u.created_at ? new Date(u.created_at) : null;
            const created = createdD ? createdD.toLocaleDateString('sv-SE')+' kl '+createdD.toLocaleTimeString('sv-SE',{hour:'2-digit',minute:'2-digit'}) : '—';
            const roleOpts = ROLE_OPTIONS.filter(r => r !== 'systemadmin' || gUserRole === 'systemadmin').map(r => '<option value="'+r+'"'+(r==='saljare'?' selected':'')+'>'+ROLE_LABELS[r]+'</option>').join('');
            return '<div style="background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:16px;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap">'
                +'<div style="flex:1;min-width:200px">'
                +'<div style="font-weight:600;font-size:15px;color:#1e293b">'+escHtml(u.name)+'</div>'
                +'<div style="font-size:13px;color:#64748b;margin-top:2px">'+escHtml(u.email)+'</div>'
                +'<div style="font-size:11px;color:#94a3b8;margin-top:4px">Registrerad: '+created+'</div>'
                +'</div>'
                +'<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">'
                +'<select id="pendingRole_'+u.id+'" style="padding:6px 10px;border:1px solid #e5e7eb;border-radius:8px;font-size:12px;font-family:inherit;background:#fff">'+roleOpts+'</select>'
                +'<button onclick="approveUser('+u.id+')" style="padding:6px 16px;background:#059669;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Godkänn</button>'
                +(gUserRole==='systemadmin'?'<button onclick="rejectUser('+u.id+',\''+escHtml(u.name)+'\')" style="padding:6px 16px;background:#ef4444;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Neka</button>':'')
                +'</div></div>';
        }).join('');
    } catch(err){
        console.error('loadAdminPending error:', err);
    }
}

async function approveUser(id){
    const roleSelect = document.getElementById('pendingRole_'+id);
    const role = roleSelect ? roleSelect.value : 'saljare';
    try {
        await fetch('/api/staff.php?id='+id, {
            method:'PUT', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({approved:1, role:role, active:1})
        });
        // Skicka välkomstmail
        try {
            const userRes = await fetch('/api/staff.php?id='+id);
            const user = await userRes.json();
            if(user && user.email){
                await fetch('/api/mail.php', {
                    method:'POST', headers:{'Content-Type':'application/json'},
                    body: JSON.stringify({
                        to: user.email,
                        subject: 'Välkommen till SolarGroup!',
                        body: '<h2>Välkommen '+escHtml(user.name)+'!</h2><p>Ditt konto har godkänts. Du kan nu logga in på <a href="https://prodconfig.wenesthosting.com">Solar Sales Suite</a>.</p><p>Din roll: <strong>'+ROLE_LABELS[role]+'</strong></p><br><p>Med vänliga hälsningar,<br>SolarGroup Admin</p>'
                    })
                });
            }
        } catch(mailErr){ console.warn('Welcome mail failed:', mailErr); }
        loadAdminPending();
        loadAdminUsers();
    } catch(err){ alert('Fel: '+err.message); }
}

async function rejectUser(id, name){
    if(gUserRole !== 'systemadmin'){ alert('Endast systemadmin kan neka användare.'); return; }
    if(!confirm('Neka och ta bort '+name+'?')) return;
    try {
        await fetch('/api/staff.php?id='+id, { method:'DELETE' });
        loadAdminPending();
    } catch(err){ alert('Fel: '+err.message); }
}

async function changeUserRole(id, role){
    try {
        await fetch('/api/staff.php?id='+id, {
            method:'PUT', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({role:role})
        });
    } catch(err){ alert('Fel: '+err.message); }
}

async function toggleUserActive(id, active){
    try {
        await fetch('/api/staff.php?id='+id, {
            method:'PUT', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({active:active})
        });
        loadAdminUsers();
    } catch(err){ alert('Fel: '+err.message); }
}

async function deleteUser(id, name){
    if(gUserRole !== 'systemadmin'){ alert('Endast systemadmin kan ta bort användare.'); return; }
    if(!confirm('Ta bort '+name+'? Detta kan inte ångras.')) return;
    try {
        await fetch('/api/staff.php?id='+id, { method:'DELETE' });
        loadAdminUsers();
    } catch(err){ alert('Fel: '+err.message); }
}

function escHtml(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }

// Ladda admin-data när sidan visas
function initAdminPage(){
    const searchEl = document.getElementById('adminUserSearch');
    if(searchEl) searchEl.value = '';
    const roleEl = document.getElementById('adminUserRoleFilter');
    if(roleEl) roleEl.value = '';
    loadAdminUsers();
    loadAdminPending();
}

// Kolla väntande vid start (för badge)
async function checkPendingUsers(){
    if(gUserRole !== 'systemadmin' && gUserRole !== 'admin' && gUserRole !== 'saljchef') return;
    try {
        const res = await fetch('/api/staff.php?approved=0&t='+Date.now());
        const pending = await res.json();
        const badge = document.getElementById('adminPendingBadge');
        if(badge){
            if(pending.length > 0){
                badge.textContent = pending.length;
                badge.style.display = 'inline';
            } else {
                badge.style.display = 'none';
            }
        }
    } catch(e){}
}

/* === BILDGENERERING === */
let bgUploadedImage = null;
let bgGeneratedImages = JSON.parse(localStorage.getItem('bgGallery') || '[]');

function handleBgUpload(input) {
    const file = input.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = function(e) {
        const img = new Image();
        img.onload = function() {
            const canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            canvas.getContext('2d').drawImage(img, 0, 0);
            bgUploadedImage = canvas.toDataURL('image/jpeg', 0.85);
            document.getElementById('bgPreviewImg').src = bgUploadedImage;
            document.getElementById('bgPreview').style.display = 'block';
        };
        img.src = e.target.result;
    };
    reader.readAsDataURL(file);
}

function clearBgUpload() {
    bgUploadedImage = null;
    document.getElementById('bgPreview').style.display = 'none';
    document.getElementById('bgFileInput').value = '';
    document.getElementById('bgCameraInput').value = '';
}