// 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">×</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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
// 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 = '';
}