js/admin.js.bak-20260416-mysalary-profile

Code: DEV-ADA62F27 Size: 19.6 KB Lines: 389 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/js/admin.js.bak-20260416-mysalary-profile

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',kunder:'Kunder',projektering:'Projektering',projekt:'Affärer',konfigurator:'Kalkyler',produkter:'Produktkatalog',ekonomi:'Ekonomi',bildgen:'Bildgenerering',personal:'Personal',kalender:'Kalender',inkorg:'Inkorg',dagrapport:'Dagrapport',minlon:'Min Lön',settings:'Inställningar'};
const PAGE_KEYS = Object.keys(PAGE_LABELS);
const PERM_ROLES = ['admin','saljchef','saljare','installator','projektledare','ekonomi'];
let permData = {};
let permSelectedRole = 'saljare';

var menuOrder = [];

async function loadPermissions(){
    try {
        const res = await fetch('/api/permissions.php');
        permData = await res.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 renderPermMatrix(){
    const container = document.getElementById('permMatrixContainer');
    const rolePerms = permData[permSelectedRole] || {};
    let html = '<div style="display:grid;grid-template-columns:1fr auto;gap:0;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden">';
    html += '<div style="padding:10px 16px;background:#f8fafc;font-weight:600;font-size:13px;color:#64748b;border-bottom:1px solid #e5e7eb">Menyval</div>';
    html += '<div style="padding:10px 16px;background:#f8fafc;font-weight:600;font-size:13px;color:#64748b;border-bottom:1px solid #e5e7eb;text-align:center">Synlig</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;' : '';
        html += `<div style="padding:10px 16px;font-size:14px;color:#1a1a1a;${border}background:${bg};display:flex;align-items:center;gap:10px">${PAGE_LABELS[key]}</div>`;
        html += `<div style="padding:10px 16px;text-align:center;${border}background:${bg}">`;
        html += `<label style="position:relative;display:inline-block;width:44px;height:24px;cursor:pointer">`;
        html += `<input type="checkbox" ${visible?'checked':''} onchange="togglePerm('${key}',this.checked)" style="opacity:0;width:0;height:0">`;
        html += `<span style="position:absolute;inset:0;background:${visible?'#10b981':'#e5e7eb'};border-radius:24px;transition:all .2s"></span>`;
        html += `<span style="position:absolute;left:${visible?'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>`;
        html += '</label></div>';
    });
    html += '</div>';
    container.innerHTML = html;
}

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 || [];
        if(gUserRole !== 'systemadmin') {
            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';
                }
            });
        }
        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 = '';
}