Files
boc/quixzoom-landing-fixed/customer-dashboard.html
T

489 lines
29 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="sv" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="light">
<meta name="apple-native-compliance" content="pass">
<title>quiXzoom — Kunddashboard</title>
<link rel="stylesheet" >
<link rel="stylesheet" >
<script defer></script>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%;color-scheme:light}
body{font-family:-apple-system,'SF Pro Text',system-ui,sans-serif;background:#F2F2F7;color:#000;-webkit-font-smoothing:antialiased;min-height:100dvh;padding-bottom:calc(83px + env(safe-area-inset-bottom,16px))}
/* Nav */
.nav-bar{position:sticky;top:0;z-index:200;padding-top:env(safe-area-inset-top,0px);background:rgba(249,249,249,.94);backdrop-filter:blur(40px) saturate(180%);-webkit-backdrop-filter:blur(40px) saturate(180%);border-bottom:.33px solid rgba(60,60,67,.29)}
.nav-inner{height:44px;display:flex;align-items:center;padding:0 16px;gap:12px}
.nav-title{flex:1;text-align:center;font-size:17px;font-weight:600;letter-spacing:-.4px}
.nav-logo{font-size:20px;font-weight:900;background:linear-gradient(135deg,#FF6B35,#F7C948);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-.5px}
.nav-btn-icon{width:32px;height:32px;border-radius: var(--radius-lg);background:#007AFF;color:#fff;border:none;font-size:14px;display:flex;align-items:center;justify-content:center;cursor:pointer}
/* Customer switcher */
.customer-switcher{background:#fff;border-bottom:.33px solid rgba(60,60,67,.12);padding:10px 16px;display:flex;gap:8px;overflow-x:auto;scrollbar-width:none}
.customer-switcher::-webkit-scrollbar{display:none}
.cust-chip{padding:7px 14px;border-radius: var(--radius-xl);font-size:13px;font-weight:600;cursor:pointer;border:none;background:#F2F2F7;color:#000;white-space:nowrap;transition:all .15s;-webkit-tap-highlight-color:transparent}
.cust-chip.active{background:#007AFF;color:#fff}
/* Large title */
.large-title-area{padding:12px 16px 8px}
.large-title{font-size:34px;font-weight:700;letter-spacing:.37px;line-height:1.1}
.large-sub{font-size:15px;color:#666666;margin-top:3px}
/* Tab bar */
.tab-bar{position:fixed;bottom:0;left:0;right:0;z-index:200;padding-bottom:env(safe-area-inset-bottom,0px);background:rgba(249,249,249,.94);backdrop-filter:blur(40px) saturate(180%);-webkit-backdrop-filter:blur(40px) saturate(180%);border-top:.33px solid rgba(60,60,67,.29);display:flex}
.tab-item{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:8px 4px 4px;cursor:pointer;gap:2px;-webkit-tap-highlight-color:transparent}
.tab-icon{font-size:22px;line-height:1}
.tab-label{font-size:10px;font-weight:500;color:#666666}
.tab-item.active .tab-label{color:#007AFF}
.tab-item:not(.active) .tab-icon{opacity:.5}
/* Content */
.content{display:none;padding-bottom:8px}
.content.active{display:block}
/* KPI strip */
.kpi-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:12px 16px 8px}
.kpi-box{background:#fff;border-radius: var(--radius-md);padding:14px 12px;text-align:center;box-shadow:0 1px 0 rgba(0,0,0,.08)}
.kpi-val{font-size:20px;font-weight:700;letter-spacing:-.5px}
.kpi-val-accent{color:#FF6B35}
.kpi-lbl{font-size:10px;color:#666666;font-weight:500;margin-top:2px}
/* Section */
.section{margin:8px 0}
.section-header{font-size:13px;font-weight:600;color:#555555;text-transform:uppercase;padding:0 32px 6px}
/* Card list */
.card-list{background:#fff;border-radius: var(--radius-md);margin:0 16px;overflow:hidden;box-shadow:0 1px 0 rgba(0,0,0,.08)}
.card-item{display:flex;align-items:center;gap:12px;padding:13px 16px;border-bottom:.33px solid rgba(60,60,67,.12);-webkit-tap-highlight-color:transparent;transition:background .1s}
.card-item:last-child{border-bottom:none}
.card-item:active{background:#F2F2F7}
.card-icon{width:40px;height:40px;border-radius: var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.ci-blue{background:#EBF5FF}.ci-green{background:#E8F8EE}.ci-orange{background:#FFF3E0}.ci-red{background:#FFE8E6}.ci-purple{background:#F4EEFF}.ci-gray{background:#F2F2F7}
.card-body{flex:1;min-width:0}
.card-title{font-size:15px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.card-sub{font-size:13px;color:#666666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.card-right{display:flex;align-items:center;gap:6px;flex-shrink:0}
.chevron{color:#C7C7CC;font-size:16px}
/* Badges */
.badge{display:inline-flex;align-items:center;padding:3px 8px;border-radius: var(--radius-sm);font-size:12px;font-weight:600;white-space:nowrap}
.bg-green{background:#E8F8EE;color:#1D7A3F}
.bg-orange{background:#FFF3E0;color:#C07000}
.bg-blue{background:#EBF5FF;color:#0051A8}
.bg-red{background:#FFE8E6;color:#C0392B}
.bg-gray{background:#F2F2F7;color:#555555}
.bg-purple{background:#F4EEFF;color:#6B2FA0}
/* Star rating */
.stars{color:#FFD60A;font-size:13px}
/* Favorite zoomer card */
.fav-card{background:#fff;border-radius: var(--radius-lg);margin:0 16px;padding:16px;display:flex;align-items:center;gap:14px;box-shadow:0 1px 0 rgba(0,0,0,.08);margin-bottom:8px}
.fav-avatar{width:52px;height:52px;border-radius: 26px;background:linear-gradient(135deg,#FF6B35,#F7C948);display:flex;align-items:center;justify-content:center;font-size:24px;flex-shrink:0}
.fav-name{font-size:16px;font-weight:700}
.fav-spec{font-size:13px;color:#666666}
.fav-meta{font-size:12px;color:#666666;margin-top:4px}
.fav-actions{margin-left:auto;display:flex;gap:8px}
/* Chart bar */
.bar-chart-wrap{padding:16px}
.bar-chart-title{font-size:15px;font-weight:600;margin-bottom:14px}
.bar-row{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.bar-label{font-size:12px;color:#555555;width:80px;flex-shrink:0}
.bar-track{flex:1;height:8px;background:#E9E9EB;border-radius: var(--radius-sm);overflow:hidden}
.bar-fill{height:8px;border-radius: var(--radius-sm);background:linear-gradient(90deg,#FF6B35,#F7C948);transition:width .7s ease}
.bar-val{font-size:12px;color:#555555;width:80px;text-align:right;flex-shrink:0}
/* Book modal */
.modal-overlay{position:fixed;inset:0;background: rgba(242,242,247,.4);z-index:300;display:none;align-items:flex-end}
.modal-overlay.open{display:flex}
.modal{background:#fff;border-radius: var(--radius-xl) 20px 0 0;padding:20px 20px calc(20px + env(safe-area-inset-bottom,0px));width:100%;max-height:90dvh;overflow-y:auto}
.modal-handle{width:36px;height:4px;border-radius: var(--radius-sm);background:#C7C7CC;margin:0 auto 16px}
.modal-title{font-size:20px;font-weight:700;margin-bottom:4px}
.modal-sub{font-size:14px;color:#666666;margin-bottom:20px}
.form-group{margin-bottom:14px}
.form-label{font-size:13px;font-weight:600;color:#555555;margin-bottom:6px;display:block}
.form-input,.form-select{width:100%;background:#F2F2F7;border:none;border-radius: var(--radius-md);padding:12px 14px;color:#000;font-size:15px;font-family:inherit;outline:none}
.btn-row{display:flex;flex-direction:column;gap:10px;margin-top:20px}
.btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:13px;border-radius: var(--radius-md);font-size:17px;font-weight:600;cursor:pointer;border:none;-webkit-tap-highlight-color:transparent;transition:opacity .15s}
.btn:active{opacity:.7}
.btn-orange{background:linear-gradient(135deg,#FF6B35,#FF9500);color:#fff}
.btn-gray{background:#F2F2F7;color:#000}
/* Empty */
.empty{padding:32px;text-align:center;color:#666666}
.empty-icon{font-size:36px;margin-bottom:8px}
/* Toast */
.toast{position:fixed;bottom:calc(83px + 12px + env(safe-area-inset-bottom,0px));left:50%;transform:translateX(-50%) translateY(60px);background:#323232;color:#fff;padding:10px 18px;border-radius: var(--radius-md);font-size:14px;font-weight:600;z-index:999;transition:transform .3s;white-space:nowrap}
.toast.show{transform:translateX(-50%) translateY(0)}
@media (max-width: 640px) {
.hero h1 { font-size: 2rem !important; }
.hero p { font-size: 1rem !important; }
.card { padding: 1.5rem !important; }
.grid { grid-template-columns: 1fr !important; }
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
}
</style>
<style id="wcag-contrast-enforcement">
/* AAMOS GLOBAL CONTRAST ENFORCEMENT — WCAG AA (4.5:1 minimum) */
/* Regel: Ingen text under 70% opacity. Aldrig. */
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
/* Fix common low-contrast patterns */
[style*="rgba(60,60,67,0.4)"] { color: rgba(60,60,67,0.65) !important; }
[style*="rgba(60,60,67,0.3)"] { color: rgba(60,60,67,0.65) !important; }
[style*="rgba(255,255,255,0.4)"] { color: rgba(255,255,255,0.72) !important; }
[style*="rgba(255,255,255,0.3)"] { color: rgba(255,255,255,0.72) !important; }
[style*="opacity: 0.4"] { opacity: 0.72 !important; }
[style*="opacity: 0.3"] { opacity: 0.72 !important; }
@media (max-width: 640px) {
.hero h1 { font-size: 2rem !important; }
.hero p { font-size: 1rem !important; }
.card { padding: 1.5rem !important; }
.grid { grid-template-columns: 1fr !important; }
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
}
</style>
</head>
<body>
<!-- Nav -->
<div class="nav-bar">
<div class="nav-inner">
<div class="nav-logo">quiX</div>
<div class="nav-title" id="nav-title">Kunddashboard</div>
<button class="btn btn-orange" style="padding:7px 14px;font-size:14px;font-weight:700;border-radius: var(--radius-md);border:none;color:#fff;background:linear-gradient(135deg,#FF6B35,#FF9500);cursor:pointer" onclick="openBookModal()">+ Boka</button>
</div>
</div>
<!-- Customer switcher -->
<div class="customer-switcher" id="customer-switcher"></div>
<!-- Large title -->
<div class="large-title-area">
<div class="large-title" id="large-title">Laddar…</div>
<div class="large-sub" id="large-sub"></div>
</div>
<!-- DASHBOARD -->
<div class="content active" id="tab-dashboard">
<div class="kpi-strip" id="kpi-strip"></div>
<div style="height:8px"></div>
<div class="section">
<div class="section-header">Aktiva uppdrag</div>
<div class="card-list" id="active-missions"></div>
</div>
<div style="height:8px"></div>
<div class="section">
<div class="section-header">Senaste uppdrag</div>
<div class="card-list" id="recent-missions"></div>
</div>
</div>
<!-- HISTORY -->
<div class="content" id="tab-history">
<div style="height:12px"></div>
<div style="background:#fff;border-radius: var(--radius-md);margin:0 16px" id="spending-chart"></div>
<div style="height:12px"></div>
<div class="section">
<div class="section-header">Alla uppdrag</div>
<div class="card-list" id="all-missions"></div>
</div>
</div>
<!-- BILLING -->
<div class="content" id="tab-billing">
<div style="height:12px"></div>
<div class="section">
<div class="section-header">Fakturor</div>
<div class="card-list" id="invoices-list"></div>
</div>
</div>
<!-- FAVORITES -->
<div class="content" id="tab-favorites">
<div style="height:12px"></div>
<div style="padding:0 16px 12px;display:flex;justify-content:flex-end">
<button class="btn btn-orange" style="font-size:14px;padding:8px 16px;border-radius: var(--radius-md);border:none;color:#fff;background:linear-gradient(135deg,#FF6B35,#FF9500);cursor:pointer" onclick="openAddFavModal()">+ Favorit</button>
</div>
<div id="favs-list"></div>
</div>
<!-- Tab bar -->
<nav class="tab-bar">
<div class="tab-item active" onclick="showTab('dashboard',this,'Hem')"><div class="tab-icon"></div><div class="tab-label">Hem</div></div>
<div class="tab-item" onclick="showTab('history',this,'Historik')"><div class="tab-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="2" width="10" height="12" rx="1" stroke="#666" stroke-width="1.5"/><path d="M6 5H10M6 8H10M6 11H8" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg></div><div class="tab-label">Historik</div></div>
<div class="tab-item" onclick="showTab('billing',this,'Fakturering')"><div class="tab-icon"></div><div class="tab-label">Fakturering</div></div>
<div class="tab-item" onclick="showTab('favorites',this,'Favoriter')"><div class="tab-icon"></div><div class="tab-label">Favoriter</div></div>
</nav>
<!-- Book modal -->
<div class="modal-overlay" id="book-modal">
<div class="modal">
<div class="modal-handle"></div>
<div class="modal-title">Boka uppdrag</div>
<div class="modal-sub" id="book-sub">Beskriv ditt uppdrag</div>
<div class="form-group"><label class="form-label">Titel</label><input class="form-input" id="bk-title" placeholder="Vad behöver du hjälp med?"></div>
<div class="form-group"><label class="form-label">Typ</label><select class="form-select" id="bk-type"><option value="photography">Fotografi </option><option value="video">Video </option><option value="drone">Drönare <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg></option></select></div>
<div class="form-group"><label class="form-label">Startdatum & tid</label><input class="form-input" type="datetime-local" id="bk-start"></div>
<div class="form-group"><label class="form-label">Duration (timmar)</label><input class="form-input" type="number" id="bk-duration" value="2" min="1"></div>
<div class="form-group"><label class="form-label">Plats</label><input class="form-input" id="bk-location" placeholder="Var ska uppdraget utföras?"></div>
<div class="form-group"><label class="form-label">Budget (SEK)</label><input class="form-input" type="number" id="bk-budget" placeholder="2500"></div>
<div class="btn-row">
<button class="btn btn-orange" onclick="submitBook()"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2L10 8L14 9L8 14L6 8L2 7L8 2Z" fill="#666"/></svg> Boka nu</button>
<button class="btn btn-gray" onclick="closeModal('book-modal')">Avbryt</button>
</div>
</div>
</div>
<!-- Add favorite modal -->
<div class="modal-overlay" id="fav-modal">
<div class="modal">
<div class="modal-handle"></div>
<div class="modal-title">Lägg till favorit</div>
<div class="modal-sub">Lägg till en zoomer som favorit</div>
<div class="form-group"><label class="form-label">Zoomer ID</label><input class="form-input" id="fav-id" placeholder="t.ex. zoomer-xyz"></div>
<div class="form-group"><label class="form-label">Namn</label><input class="form-input" id="fav-name" placeholder="Förnamn E."></div>
<div class="form-group"><label class="form-label">Specialitet</label><select class="form-select" id="fav-spec"><option>Photography</option><option>Video</option><option>Drone</option><option>General</option></select></div>
<div class="btn-row">
<button class="btn btn-orange" onclick="submitFav()"> Lägg till</button>
<button class="btn btn-gray" onclick="closeModal('fav-modal')">Avbryt</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
// State
const CUSTOMERS = ['cust-101','cust-102','cust-103'];
let activeCustomer = 'cust-101';
let customerCache = {};
let missionCache = {};
let spendingCache = {};
let invoiceCache = {};
let favCache = {};
// Utils
function fmtMoney(n){return new Intl.NumberFormat('sv-SE').format(n)+' kr'}
function fmtDate(s){if(!s)return'—';return new Date(s).toLocaleDateString('sv-SE',{day:'2-digit',month:'short'})}
function fmtDateLong(s){if(!s)return'—';return new Date(s).toLocaleDateString('sv-SE',{day:'2-digit',month:'long',year:'numeric'})}
function toast(msg){const el=document.getElementById('toast');el.textContent=msg;el.classList.add('show');setTimeout(()=>el.classList.remove('show'),2500)}
function statusBadge(s){return{completed:'<span class="badge bg-green"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8L6.5 11.5L13 4.5" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Klar</span>',active:'<span class="badge bg-blue"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 8C2 11.3 4.7 14 8 14C10.5 14 12.7 12.5 13.7 10.3" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M14 8C14 4.7 11.3 2 8 2C5.5 2 3.3 3.5 2.3 5.7" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M13.5 7.5L14 10L11.5 9.5" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M2.5 8.5L2 6L4.5 6.5" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Aktiv</span>',open:'<span class="badge bg-orange"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="7" cy="7" r="5" stroke="#666" stroke-width="2"/><path d="M11 11L14 14" stroke="#666" stroke-width="2" stroke-linecap="round"/></svg> Öppen</span>',cancelled:'<span class="badge bg-red"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M4 4L12 12M12 4L4 12" stroke="#ef4444" stroke-width="2" stroke-linecap="round"/></svg> Avbruten</span>'}[s]||`<span class="badge bg-gray">${s}</span>`}
function missionIcon(t){return{photography:'',video:'',drone:'<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2V6M4 4H12" stroke="#666" stroke-width="1.5" stroke-linecap="round"/><path d="M2 8H6L8 6L10 8H14" stroke="#666" stroke-width="1.5" stroke-linejoin="round"/><path d="M6 8V12H10V8" stroke="#666" stroke-width="1.5"/></svg>'}[t]||'<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="4" width="12" height="9" rx="2" stroke="#666" stroke-width="1.5"/><circle cx="8" cy="8.5" r="2.5" stroke="#666" stroke-width="1.5"/><path d="M5 4L6 2H10L11 4" stroke="#666" stroke-width="1.5"/></svg>'}
function missionIconClass(s){return{completed:'ci-green',active:'ci-blue',open:'ci-orange',cancelled:'ci-red'}[s]||'ci-gray'}
function stars(r){if(!r)return'<span style="color:#666666;font-size:12px">Väntar</span>';return'<span class="stars">'+(''.repeat(Math.round(r))+''.repeat(5-Math.round(r)))+'</span><span style="font-size:12px;color:#666666;margin-left:4px">'+r+'</span>'}
function invStatusBadge(s){return s==='paid'?'<span class="badge bg-green">Betald</span>':'<span class="badge bg-orange">Obetald</span>'}
function closeModal(id){document.getElementById(id).classList.remove('open')}
document.querySelectorAll('.modal-overlay').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.remove('open')}));
// Tab
function showTab(id,el,title){
document.querySelectorAll('.content').forEach(c=>c.classList.remove('active'));
document.querySelectorAll('.tab-item').forEach(t=>t.classList.remove('active'));
document.getElementById('tab-'+id).classList.add('active');
el.classList.add('active');
document.getElementById('nav-title').textContent=title;
if(id==='history')renderHistory();
if(id==='billing')renderBilling();
if(id==='favorites')renderFavorites();
}
// Customer switcher
function renderSwitcher(){
const c = Object.values(customerCache);
document.getElementById('customer-switcher').innerHTML = c.map(cust=>`
<button class="cust-chip${cust.id===activeCustomer?' active':''}" onclick="switchCustomer('${cust.id}')">${cust.name.split(' ').slice(0,2).join(' ')}</button>
`).join('');
}
async function switchCustomer(id){
activeCustomer=id;
renderSwitcher();
await loadCustomerData(id);
renderDashboard();
}
// Data loading
async function loadAll(){
// Load all customers
const custR=await fetch('https://api.quixzoom.com/api/qz/customers').then(r=>r.json()).catch(()=>({ok:false}));
if(custR.ok){
custR.items.forEach(c=>customerCache[c.id]=c);
renderSwitcher();
}
await loadCustomerData(activeCustomer);
renderDashboard();
}
async function loadCustomerData(id){
const [mR,sR,iR,fR]=await Promise.all([
fetch(`/api/quixzoom/customers/${id}/missions`).then(r=>r.json()).catch(()=>({ok:false})),
fetch(`/api/quixzoom/customers/${id}/spending`).then(r=>r.json()).catch(()=>({ok:false})),
fetch(`/api/quixzoom/customers/${id}/invoices`).then(r=>r.json()).catch(()=>({ok:false})),
fetch(`/api/quixzoom/customers/${id}/favorites`).then(r=>r.json()).catch(()=>({ok:false})),
]);
if(mR.ok)missionCache[id]=mR.items||[];
if(sR.ok)spendingCache[id]=sR.spending;
if(iR.ok)invoiceCache[id]=iR.items||[];
if(fR.ok)favCache[id]=fR.items||[];
}
// Dashboard
function renderDashboard(){
const cust=customerCache[activeCustomer];
const missions=missionCache[activeCustomer]||[];
const spending=spendingCache[activeCustomer]||{};
if(cust){
document.getElementById('large-title').textContent=cust.name;
document.getElementById('large-sub').textContent=`${cust.plan.toUpperCase()} · ${cust.contact}`;
document.getElementById('book-sub').textContent=`Bokar åt ${cust.name}`;
}
// KPIs
const active=missions.filter(m=>m.status==='active'||m.status==='open');
const completed=missions.filter(m=>m.status==='completed');
document.getElementById('kpi-strip').innerHTML=`
<div class="kpi-box"><div class="kpi-val kpi-val-accent">${active.length}</div><div class="kpi-lbl">Aktiva</div></div>
<div class="kpi-box"><div class="kpi-val">${completed.length}</div><div class="kpi-lbl">Avslutade</div></div>
<div class="kpi-box"><div class="kpi-val">${fmtMoney(spending.total_completed_sek||0).replace(' kr','')}</div><div class="kpi-lbl">Totalt (kr)</div></div>
`;
// Active missions
const activeMissions=missions.filter(m=>['active','open'].includes(m.status));
document.getElementById('active-missions').innerHTML=activeMissions.length
? activeMissions.map(renderMissionCard).join('')
: '<div class="empty"><div class="empty-icon"></div><div style="font-weight:600">Inga aktiva uppdrag</div><div style="font-size:13px;margin-top:4px">Boka ett nytt uppdrag!</div></div>';
// Recent (completed, max 3)
const recent=missions.filter(m=>m.status==='completed').slice(0,3);
document.getElementById('recent-missions').innerHTML=recent.length
? recent.map(renderMissionCard).join('')
: '<div class="empty"><div class="empty-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="2" width="10" height="12" rx="1" stroke="#666" stroke-width="1.5"/><path d="M6 5H10M6 8H10M6 11H8" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg></div><div style="font-weight:600">Inga avslutade uppdrag</div></div>';
}
function renderMissionCard(m){
return `<div class="card-item">
<div class="card-icon ${missionIconClass(m.status)}">${missionIcon(m.type)}</div>
<div class="card-body">
<div class="card-title">${m.title}</div>
<div class="card-sub">${m.location} · ${fmtDate(m.start_time)}${m.zoomer_name?' · '+m.zoomer_name:''}</div>
</div>
<div class="card-right">
${statusBadge(m.status)}
<div style="text-align:right;margin-left:6px"><div style="font-size:14px;font-weight:600">${fmtMoney(m.amount_sek)}</div>${m.zoomer_rating?stars(m.zoomer_rating):''}</div>
<span class="chevron"></span>
</div>
</div>`;
}
// History tab
function renderHistory(){
const missions=missionCache[activeCustomer]||[];
const spending=spendingCache[activeCustomer]||{};
// Spending chart
const byMonth=spending.by_month||{};
const months=Object.keys(byMonth).sort();
const maxVal=Math.max(...Object.values(byMonth),1);
document.getElementById('spending-chart').innerHTML=`
<div class="bar-chart-wrap">
<div class="bar-chart-title">Utgifter per månad</div>
${months.map(mo=>`
<div class="bar-row">
<div class="bar-label">${mo.slice(5)}/${mo.slice(2,4)}</div>
<div class="bar-track"><div class="bar-fill" style="width:${Math.round(byMonth[mo]/maxVal*100)}%"></div></div>
<div class="bar-val">${fmtMoney(byMonth[mo])}</div>
</div>`).join('')}
${!months.length?'<div style="color:#666666;font-size:14px">Ingen historik ännu</div>':''}
</div>`;
// All missions
document.getElementById('all-missions').innerHTML=missions.length
? missions.map(renderMissionCard).join('')
: '<div class="empty"><div class="empty-icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="2" width="10" height="12" rx="1" stroke="#666" stroke-width="1.5"/><path d="M6 5H10M6 8H10M6 11H8" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg></div>Inga uppdrag</div>';
}
// Billing tab
function renderBilling(){
const invs=invoiceCache[activeCustomer]||[];
document.getElementById('invoices-list').innerHTML=invs.length
? invs.map(inv=>`
<div class="card-item">
<div class="card-icon ${inv.status==='paid'?'ci-green':'ci-orange'}">${inv.status==='paid'?'<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>':''}</div>
<div class="card-body"><div class="card-title">${inv.number}</div><div class="card-sub">Förfaller ${fmtDate(inv.due_date)}</div></div>
<div class="card-right">${invStatusBadge(inv.status)}<div style="font-size:14px;font-weight:700;margin-left:8px">${fmtMoney(inv.amount_sek)}</div><span class="chevron"></span></div>
</div>`).join('')
: '<div class="empty"><div class="empty-icon"></div>Inga fakturor</div>';
}
// Favorites tab
function renderFavorites(){
const favs=favCache[activeCustomer]||[];
document.getElementById('favs-list').innerHTML=favs.length
? favs.map(f=>`
<div class="fav-card">
<div class="fav-avatar">${f.avatar||'<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="4" width="12" height="9" rx="2" stroke="#666" stroke-width="1.5"/><circle cx="8" cy="8.5" r="2.5" stroke="#666" stroke-width="1.5"/><path d="M5 4L6 2H10L11 4" stroke="#666" stroke-width="1.5"/></svg>'}</div>
<div>
<div class="fav-name">${f.name}</div>
<div class="fav-spec">${f.specialty}</div>
<div class="fav-meta">${f.missions_completed} uppdrag · ${f.rating?' '+f.rating:'Ny'}</div>
</div>
<div class="fav-actions">
<button class="btn btn-gray" style="font-size:13px;padding:7px 12px;border-radius: var(--radius-md);border:none;cursor:pointer" onclick="removeFav('${f.zoomer_id}')"></button>
</div>
</div>`).join('')
: '<div class="empty"><div class="empty-icon"></div><div style="font-weight:600">Inga favoriter</div><div style="font-size:13px;margin-top:4px">Lägg till zoomers du gillar!</div></div>';
}
async function removeFav(zid){
const r=await fetch(`/api/quixzoom/customers/${activeCustomer}/favorites/${zid}`,{method:'DELETE'}).then(r=>r.json());
if(r.ok){
toast('Favorit borttagen');
const fR=await fetch(`/api/quixzoom/customers/${activeCustomer}/favorites`).then(r=>r.json());
favCache[activeCustomer]=fR.items||[];
renderFavorites();
} else toast('Fel: '+r.error);
}
// Book modal
function openBookModal(){document.getElementById('book-modal').classList.add('open')}
async function submitBook(){
const title=document.getElementById('bk-title').value;
const start=document.getElementById('bk-start').value;
if(!title||!start){toast('Fyll i titel och startdatum');return}
const body={
title,type:document.getElementById('bk-type').value,
start_time:new Date(start).toISOString(),
duration_hours:+document.getElementById('bk-duration').value||2,
location:document.getElementById('bk-location').value||'Phuket',
budget_sek:+document.getElementById('bk-budget').value||2500,
};
const r=await fetch(`/api/quixzoom/customers/${activeCustomer}/book`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}).then(r=>r.json());
if(r.ok){
toast('<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 2L10 8L14 9L8 14L6 8L2 7L8 2Z" fill="#666"/></svg> Uppdrag bokat!');closeModal('book-modal');
const mR=await fetch(`/api/quixzoom/customers/${activeCustomer}/missions`).then(r=>r.json());
missionCache[activeCustomer]=mR.items||[];renderDashboard();
document.getElementById('bk-title').value='';document.getElementById('bk-location').value='';document.getElementById('bk-budget').value='';
} else toast('Fel: '+r.error);
}
// Add favorite modal
function openAddFavModal(){document.getElementById('fav-modal').classList.add('open')}
async function submitFav(){
const body={zoomer_id:document.getElementById('fav-id').value,name:document.getElementById('fav-name').value,specialty:document.getElementById('fav-spec').value};
if(!body.zoomer_id||!body.name){toast('Fyll i ID och namn');return}
const r=await fetch(`/api/quixzoom/customers/${activeCustomer}/favorites`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}).then(r=>r.json());
if(r.ok){
toast(' Favorit tillagd!');closeModal('fav-modal');
const fR=await fetch(`/api/quixzoom/customers/${activeCustomer}/favorites`).then(r=>r.json());
favCache[activeCustomer]=fR.items||[];renderFavorites();
document.getElementById('fav-id').value='';document.getElementById('fav-name').value='';
} else toast('Fel: '+r.error);
}
// Init
loadAll();
</script>
</body>
</html>