2026-07-05 06:41:32 +00:00
<!DOCTYPE html>
< html lang = "sv" data-theme = "light" >
< head >
< link rel = "stylesheet" >
< link rel = "stylesheet" >
< script defer > < / script >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, viewport-fit=cover" >
< meta name = "brand-tier" content = "quixzoom" >
< meta name = "module" content = "revenue-dashboard" >
< meta name = "last-audit" content = "2026-05-18T23:50:00Z" >
< title > quiXzoom — Revenue Dashboard< / title >
< style >
* { box-sizing : border-box ; margin : 0 ; padding : 0 }
: root { --bg : #F2F2F7 ; --navy : #007AFF ; --accent : #FF6B35 ; --accent2 : #FF8C5A ; --text : #1C1C1E ; --text2 : #4A5568 ; --text3 : #8A9BB0 ; --border : #E8E3DC ; --card : #FFFFFF ; --surface : #F4F1EC ; --green : #16a34a ; --yellow : #d97706 ; --red : #dc2626 ; --blue : #2563eb ; --purple : #7c3aed ; --sidebar : 260 px ; --qx : #FF6B35 }
html , body { height : 100 % ; background : var ( - - bg ) ; color : var ( - - text ) ; font-family : - apple-system , 'Inter' , system-ui , sans-serif ; font-size : 14 px ; line-height : 1.5 ; -webkit- font-smoothing : antialiased }
. layout { display : flex ; height : 100 vh ; overflow : hidden }
. sidebar { width : var ( - - sidebar ) ; background : #1A1A2E ; display : flex ; flex-direction : column ; flex-shrink : 0 ; overflow-y : auto }
. sidebar-logo { padding : 20 px 20 px 16 px ; border-bottom : 1 px solid rgba ( 255 , 255 , 255 , .08 ) }
. sidebar-logo . brand { font-size : 10 px ; font-weight : 700 ; letter-spacing : .14 em ; color : rgba ( 255 , 255 , 255 , .75 ) ; text-transform : uppercase ; margin-bottom : 4 px }
. sidebar-logo . name { font-size : 21 px ; font-weight : 900 ; color : #fff ; letter-spacing : -.03 em }
. sidebar-logo . name span { color : var ( - - qx ) }
. sidebar-logo . meta { font-size : 11 px ; color : rgba ( 255 , 255 , 255 , .75 ) ; margin-top : 3 px }
. sidebar-section { padding : 16 px 12 px 6 px }
. sidebar-section-label { font-size : 10 px ; font-weight : 700 ; letter-spacing : .1 em ; color : rgba ( 255 , 255 , 255 , .75 ) ; text-transform : uppercase ; padding : 0 8 px ; margin-bottom : 6 px }
2026-07-07 07:11:50 +00:00
. sidebar-item { display : flex ; align-items : center ; gap : 10 px ; padding : 9 px 10 px ; border-radius : var ( - - radius - md ) ; cursor : pointer ; color : rgba ( 255 , 255 , 255 , .6 ) ; font-size : 13 px ; font-weight : 500 ; transition : all .15 s ; border : none ; background : none ; width : 100 % ; text-align : left }
2026-07-05 06:41:32 +00:00
. sidebar-item : hover { background : rgba ( 255 , 255 , 255 , .08 ) ; color : #fff }
. sidebar-item . active { background : var ( - - qx ) ; color : #fff }
. sidebar-item . icon { font-size : 15 px ; width : 22 px ; text-align : center ; flex-shrink : 0 }
. sidebar-footer { margin-top : auto ; padding : 16 px 12 px ; border-top : 1 px solid rgba ( 255 , 255 , 255 , .08 ) }
. main { flex : 1 ; overflow-y : auto ; display : flex ; flex-direction : column ; min-width : 0 }
. topbar { background : var ( - - card ) ; border-bottom : 1 px solid var ( - - border ) ; padding : 0 28 px ; height : 56 px ; display : flex ; align-items : center ; justify-content : space-between ; flex-shrink : 0 ; position : sticky ; top : 0 ; z-index : 10 }
. topbar-left { display : flex ; align-items : center ; gap : 12 px }
. topbar-title { font-size : 16 px ; font-weight : 700 }
. topbar-breadcrumb { font-size : 12 px ; color : var ( - - text3 ) }
. topbar-actions { display : flex ; gap : 8 px ; align-items : center }
. content { padding : 24 px 28 px ; flex : 1 }
2026-07-07 07:11:50 +00:00
. tabs { display : flex ; gap : 4 px ; background : var ( - - surface ) ; border-radius : var ( - - radius - md ) ; padding : 4 px ; border : 1 px solid var ( - - border ) ; width : fit-content ; margin-bottom : 20 px }
. tab { padding : 7 px 18 px ; border-radius : var ( - - radius - sm ) ; font-size : 13 px ; font-weight : 600 ; cursor : pointer ; color : var ( - - text2 ) ; border : none ; background : none ; transition : all .15 s }
2026-07-05 06:41:32 +00:00
. tab . active { background : var ( - - card ) ; color : var ( - - text ) ; box-shadow : 0 1 px 4 px rgba ( 0 , 0 , 0 , .08 ) }
. view { display : none } . view . active { display : block }
2026-07-07 07:11:50 +00:00
. btn { display : inline-flex ; align-items : center ; gap : 6 px ; padding : 8 px 16 px ; border-radius : var ( - - radius - md ) ; font-size : 13 px ; font-weight : 600 ; cursor : pointer ; border : none ; transition : all .15 s }
2026-07-05 06:41:32 +00:00
. btn-primary { background : var ( - - qx ) ; color : #fff } . btn-primary : hover { background : var ( - - accent2 ) }
. btn-secondary { background : var ( - - surface ) ; color : var ( - - text ) ; border : 1 px solid var ( - - border ) } . btn-secondary : hover { background : var ( - - border ) }
. btn-sm { padding : 5 px 12 px ; font-size : 12 px }
. btn-xs { padding : 3 px 8 px ; font-size : 11 px }
. stats-grid { display : grid ; grid-template-columns : repeat ( auto - fit , minmax ( 160 px , 1 fr ) ) ; gap : 14 px ; margin-bottom : 24 px }
2026-07-07 07:11:50 +00:00
. stat-card { background : var ( - - card ) ; border : 1 px solid var ( - - border ) ; border-radius : var ( - - radius - md ) ; padding : 18 px 20 px ; position : relative ; overflow : hidden }
. stat-card :: after { content : '' ; position : absolute ; top : 0 ; left : 0 ; right : 0 ; height : 3 px ; background : var ( - - qx ) ; border-radius : var ( - - radius - sm ) 3 px 0 0 }
2026-07-05 06:41:32 +00:00
. stat-card . g :: after { background : var ( - - green ) } . stat-card . r :: after { background : var ( - - red ) } . stat-card . b :: after { background : var ( - - blue ) } . stat-card . p :: after { background : var ( - - purple ) } . stat-card . y :: after { background : var ( - - yellow ) }
. stat-card . label { font-size : 10 px ; font-weight : 700 ; color : var ( - - text3 ) ; text-transform : uppercase ; letter-spacing : .08 em ; margin-bottom : 6 px }
. stat-card . value { font-size : 24 px ; font-weight : 800 ; letter-spacing : -.02 em ; line-height : 1 }
. stat-card . sub { font-size : 11 px ; color : var ( - - text3 ) ; margin-top : 5 px }
2026-07-07 07:11:50 +00:00
. stat-card . change { font-size : 11 px ; font-weight : 700 ; padding : 2 px 7 px ; border-radius : var ( - - radius - xl ) ; display : inline-block ; margin-top : 5 px }
2026-07-05 06:41:32 +00:00
. change-up { background : #dcfce7 ; color : #166534 } . change-flat { background : #f1f5f9 ; color : #475569 }
2026-07-07 07:11:50 +00:00
. badge { display : inline-flex ; align-items : center ; gap : 4 px ; padding : 2 px 9 px ; border-radius : var ( - - radius - xl ) ; font-size : 11 px ; font-weight : 700 ; white-space : nowrap }
2026-07-05 06:41:32 +00:00
. badge-green { background : #dcfce7 ; color : #166534 } . badge-red { background : #fee2e2 ; color : #991b1b } . badge-yellow { background : #fef9c3 ; color : #854d0e } . badge-blue { background : #dbeafe ; color : #1e40af } . badge-gray { background : #f1f5f9 ; color : #475569 } . badge-orange { background : #ffedd5 ; color : #9a3412 } . badge-purple { background : #ede9fe ; color : #4c1d95 }
2026-07-07 07:11:50 +00:00
. table-wrap { background : var ( - - card ) ; border : 1 px solid var ( - - border ) ; border-radius : var ( - - radius - md ) ; overflow : hidden ; margin-bottom : 20 px }
2026-07-05 06:41:32 +00:00
. table-header { display : flex ; align-items : center ; justify-content : space-between ; padding : 14 px 20 px ; border-bottom : 1 px solid var ( - - border ) ; gap : 10 px ; flex-wrap : wrap }
. table-header h3 { font-size : 14 px ; font-weight : 700 }
. table-header-actions { display : flex ; gap : 8 px ; align-items : center ; flex-wrap : wrap }
table { width : 100 % ; border-collapse : collapse }
thead th { padding : 9 px 14 px ; text-align : left ; font-size : 10 px ; font-weight : 700 ; color : var ( - - text3 ) ; text-transform : uppercase ; letter-spacing : .06 em ; background : var ( - - surface ) ; border-bottom : 1 px solid var ( - - border ) ; white-space : nowrap }
thead th . right , td . right { text-align : right }
tbody tr { border-bottom : 1 px solid rgba ( 232 , 227 , 220 , .6 ) ; transition : background .1 s }
tbody tr : last-child { border-bottom : none }
tbody tr : hover { background : #fafaf8 }
td { padding : 11 px 14 px ; font-size : 13 px ; vertical-align : middle }
td . mono { font-variant-numeric : tabular-nums ; font-family : 'SF Mono' , monospace ; font-size : 12 px }
. tbl-scroll { overflow-x : auto }
. section-title { font-size : 13 px ; font-weight : 700 ; color : var ( - - text2 ) ; margin-bottom : 14 px ; padding-bottom : 8 px ; border-bottom : 1 px solid var ( - - border ) }
/* CHARTS */
2026-07-07 07:11:50 +00:00
. chart-card { background : var ( - - card ) ; border : 1 px solid var ( - - border ) ; border-radius : var ( - - radius - md ) ; padding : 20 px ; margin-bottom : 20 px }
2026-07-05 06:41:32 +00:00
. chart-card h3 { font-size : 14 px ; font-weight : 700 ; margin-bottom : 16 px }
. bar-chart { display : flex ; align-items : flex-end ; gap : 6 px ; height : 120 px ; padding-top : 8 px }
. bar-wrap { flex : 1 ; display : flex ; flex-direction : column ; align-items : center ; gap : 4 px }
2026-07-07 07:11:50 +00:00
. bar { width : 100 % ; border-radius : var ( - - radius - sm ) 4 px 0 0 ; background : var ( - - qx ) ; opacity : .8 ; transition : opacity .15 s ; cursor : pointer ; position : relative }
2026-07-05 06:41:32 +00:00
. bar : hover { opacity : 1 }
. bar-label { font-size : 10 px ; color : var ( - - text3 ) ; white-space : nowrap }
. bar-value { font-size : 10 px ; font-weight : 700 ; color : var ( - - text2 ) }
/* ZOOMER CARDS */
. zoomer-grid { display : grid ; grid-template-columns : repeat ( auto - fill , minmax ( 280 px , 1 fr ) ) ; gap : 14 px ; margin-bottom : 20 px }
2026-07-07 07:11:50 +00:00
. zoomer-card { background : var ( - - card ) ; border : 1 px solid var ( - - border ) ; border-radius : var ( - - radius - md ) ; padding : 18 px ; transition : box-shadow .15 s }
2026-07-05 06:41:32 +00:00
. zoomer-card : hover { box-shadow : 0 4 px 16 px rgba ( 0 , 0 , 0 , .07 ) }
. zoomer-card . zc-header { display : flex ; align-items : center ; gap : 12 px ; margin-bottom : 12 px }
2026-07-07 07:11:50 +00:00
. zc-avatar { width : 40 px ; height : 40 px ; border-radius : var ( - - radius - full ) ; background : linear-gradient ( 135 deg , var ( - - qx ) , var ( - - purple ) ) ; display : flex ; align-items : center ; justify-content : center ; color : #fff ; font-weight : 800 ; font-size : 15 px ; flex-shrink : 0 }
2026-07-05 06:41:32 +00:00
. zc-name { font-size : 14 px ; font-weight : 700 }
. zc-role { font-size : 11 px ; color : var ( - - text3 ) }
. zc-stats { display : grid ; grid-template-columns : 1 fr 1 fr 1 fr ; gap : 8 px }
2026-07-07 07:11:50 +00:00
. zc-stat { text-align : center ; background : var ( - - surface ) ; border-radius : var ( - - radius - md ) ; padding : 8 px 4 px }
2026-07-05 06:41:32 +00:00
. zc-stat . cs-val { font-size : 15 px ; font-weight : 800 ; color : var ( - - text ) }
. zc-stat . cs-lbl { font-size : 9 px ; font-weight : 700 ; text-transform : uppercase ; color : var ( - - text3 ) ; letter-spacing : .06 em }
/* GEO */
. geo-grid { display : grid ; grid-template-columns : repeat ( auto - fit , minmax ( 180 px , 1 fr ) ) ; gap : 12 px }
2026-07-07 07:11:50 +00:00
. geo-card { background : var ( - - card ) ; border : 1 px solid var ( - - border ) ; border-radius : var ( - - radius - md ) ; padding : 16 px }
2026-07-05 06:41:32 +00:00
. geo-card . gc-name { font-size : 13 px ; font-weight : 700 ; margin-bottom : 6 px }
2026-07-07 07:11:50 +00:00
. geo-bar-wrap { height : 6 px ; background : var ( - - surface ) ; border-radius : var ( - - radius - sm ) ; margin-bottom : 6 px ; overflow : hidden }
. geo-bar { height : 100 % ; background : var ( - - qx ) ; border-radius : var ( - - radius - sm ) ; transition : width .5 s }
2026-07-05 06:41:32 +00:00
. geo-meta { font-size : 11 px ; color : var ( - - text2 ) }
/* DAC7 */
2026-07-07 07:11:50 +00:00
. dac7-panel { background : var ( - - card ) ; border : 1 px solid var ( - - border ) ; border-radius : var ( - - radius - md ) ; padding : 20 px ; margin-bottom : 20 px }
. dac7-donut { width : 120 px ; height : 120 px ; border-radius : var ( - - radius - full ) ; position : relative ; margin : 0 auto 12 px }
. compliance-meter { height : 12 px ; background : var ( - - surface ) ; border-radius : var ( - - radius - sm ) ; overflow : hidden ; margin-bottom : 8 px }
. compliance-fill { height : 100 % ; border-radius : var ( - - radius - sm ) ; background : var ( - - green ) ; transition : width .5 s }
2026-07-05 06:41:32 +00:00
/* PAYOUT */
2026-07-07 07:11:50 +00:00
. payout-card { background : linear-gradient ( 135 deg , #1A1A2E , #16213E ) ; color : #fff ; border-radius : var ( - - radius - lg ) ; padding : 24 px ; margin-bottom : 20 px }
2026-07-05 06:41:32 +00:00
. payout-card . pc-title { font-size : 12 px ; font-weight : 700 ; letter-spacing : .1 em ; text-transform : uppercase ; color : rgba ( 255 , 255 , 255 , .75 ) ; margin-bottom : 8 px }
. payout-card . pc-amount { font-size : 36 px ; font-weight : 900 ; letter-spacing : -.02 em ; margin-bottom : 4 px }
. payout-card . pc-sub { font-size : 13 px ; color : rgba ( 255 , 255 , 255 , .6 ) }
@ media ( max-width : 640px ) {
. hero h1 { font-size : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !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 : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !important ; }
}
< / style >
< / head >
< body >
< div class = "layout" >
<!-- SIDEBAR -->
< nav class = "sidebar" >
< div class = "sidebar-logo" >
< div class = "brand" > AAMOS · quiXzoom< / div >
< div class = "name" > quiX< span > zoom< / span > < / div >
< div class = "meta" > Revenue Dashboard< / div >
< / div >
< div class = "sidebar-section" >
< div class = "sidebar-section-label" > quiXzoom< / div >
2026-07-07 07:11:50 +00:00
< button class = "sidebar-item" onclick = "location.href='launch.html'" > < span class = "icon" > < 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 > < / span > Launch Center< / button >
< button class = "sidebar-item active" > < span class = "icon" > < / span > Revenue Dashboard< / button >
< button class = "sidebar-item" onclick = "location.href='admin.html'" > < span class = "icon" > < svg width = "16" height = "16" viewBox = "0 0 16 16" fill = "none" > < circle cx = "8" cy = "8" r = "3" stroke = "#666" stroke-width = "1.5" / > < path d = "M8 2V4M8 12V14M2 8H4M12 8H14M4.3 4.3L5.7 5.7M10.3 10.3L11.7 11.7M4.3 11.7L5.7 10.3M10.3 5.7L11.7 4.3" stroke = "#666" stroke-width = "1.5" stroke-linecap = "round" / > < / svg > < / span > Admin< / button >
< button class = "sidebar-item" onclick = "location.href='dac7-reporting.html'" > < span class = "icon" > < / span > DAC7 Reporting< / button >
< button class = "sidebar-item" onclick = "location.href='zoomer-earnings.html'" > < span class = "icon" > < / span > Zoomer Earnings< / button >
2026-07-05 06:41:32 +00:00
< / div >
< div class = "sidebar-section" >
< div class = "sidebar-section-label" > Utbetalningar< / div >
2026-07-07 07:11:50 +00:00
< button class = "sidebar-item" > < span class = "icon" > < 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 > < / span > Godkända< / button >
2026-07-05 06:41:32 +00:00
< button class = "sidebar-item" > < span class = "icon" > ⏳< / span > Väntande< / button >
2026-07-07 07:11:50 +00:00
< button class = "sidebar-item" > < span class = "icon" > < svg width = "20" height = "20" viewBox = "0 0 20 20" fill = "none" > < circle cx = "10" cy = "10" r = "9" stroke = "#ef4444" stroke-width = "2" / > < path d = "M7 7L13 13M13 7L7 13" stroke = "#ef4444" stroke-width = "2" stroke-linecap = "round" / > < / svg > < / span > Misslyckade< / button >
2026-07-05 06:41:32 +00:00
< / div >
< div class = "sidebar-footer" >
< button class = "sidebar-item" onclick = "location.href='../ouroboros/index.html'" > < span class = "icon" > ←< / span > Ouroboros< / button >
< / div >
< / nav >
<!-- MAIN -->
< div class = "main" >
< div class = "topbar" >
< div class = "topbar-left" >
< span class = "topbar-breadcrumb" > quiXzoom< / span >
< span style = "color:var(--text3)" > › < / span >
2026-07-07 07:11:50 +00:00
< span class = "topbar-title" > Revenue Dashboard< / span >
2026-07-05 06:41:32 +00:00
< / div >
< div class = "topbar-actions" >
< select class = "btn btn-secondary btn-sm" id = "periodFilter" onchange = "renderAll()" style = "border:1px solid var(--border)" >
< option value = "all" > Alla perioder< / option >
< option value = "may2026" > Maj 2026< / option >
< option value = "apr2026" > April 2026< / option >
< option value = "q2" > Q2 2026< / option >
< / select >
2026-07-07 07:11:50 +00:00
< button class = "btn btn-primary btn-sm" onclick = "exportCSV()" > Exportera CSV< / button >
2026-07-05 06:41:32 +00:00
< / div >
< / div >
< div class = "content" >
<!-- TOP STATS -->
< div class = "stats-grid" id = "topStats" >
< div class = "stat-card" >
< div class = "label" > Totalt intjänat< / div >
< div class = "value" id = "statTotal" > 0 kr< / div >
< div class = "sub" > Alla zoomers< / div >
< span class = "change change-up" id = "statTotalChange" > +0%< / span >
< / div >
< div class = "stat-card g" >
< div class = "label" > Aktiva zoomers< / div >
< div class = "value" id = "statActive" > 0< / div >
< div class = "sub" > Senaste 30 dagar< / div >
< / div >
< div class = "stat-card b" >
< div class = "label" > Uppdrag (totalt)< / div >
< div class = "value" id = "statAssignments" > 0< / div >
< div class = "sub" > Genomförda< / div >
< / div >
< div class = "stat-card y" >
< div class = "label" > Snitt per zoomer< / div >
< div class = "value" id = "statAvg" > 0 kr< / div >
< div class = "sub" > Intjänat/zoomer< / div >
< / div >
< div class = "stat-card p" >
< div class = "label" > Pending utbetalning< / div >
< div class = "value" id = "statPending" > 0 kr< / div >
< div class = "sub" > Ej utbetalt ännu< / div >
< / div >
< div class = "stat-card r" >
< div class = "label" > DAC7 Compliance< / div >
< div class = "value" id = "statDac7" > 0%< / div >
< div class = "sub" > Verifierade TIN< / div >
< / div >
< / div >
<!-- TABS -->
< div class = "tabs" >
2026-07-07 07:11:50 +00:00
< button class = "tab active" onclick = "switchTab('overview',this)" > Översikt< / button >
< button class = "tab" onclick = "switchTab('zoomers',this)" > Per zoomer< / button >
< button class = "tab" onclick = "switchTab('payouts',this)" > Utbetalningar< / button >
< button class = "tab" onclick = "switchTab('geo',this)" > Geografi< / button >
< button class = "tab" onclick = "switchTab('dac7',this)" > DAC7< / button >
2026-07-05 06:41:32 +00:00
< / div >
<!-- OVERVIEW -->
< div class = "view active" id = "view-overview" >
<!-- PAYOUT NEXT -->
< div class = "payout-card" >
< div class = "pc-title" > Nästa utbetalningsdatum< / div >
< div class = "pc-amount" id = "pcAmount" > 132 450 kr< / div >
2026-07-07 07:11:50 +00:00
< div class = "pc-sub" > < span id = "pcDate" > 1 augusti 2026< / span > — < span id = "pcCount" > 23 zoomers< / span > väntar på utbetalning< / div >
2026-07-05 06:41:32 +00:00
< div style = "margin-top:16px;display:flex;gap:8px" >
2026-07-07 07:11:50 +00:00
< button class = "btn btn-sm" style = "background:var(--qx);color:#fff" > < 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 > Initiera utbetalning< / button >
< button class = "btn btn-sm" style = "background:rgba(255,255,255,.15);color:#fff;border:1px solid rgba(255,255,255,.2)" > < 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 > Granska lista< / button >
2026-07-05 06:41:32 +00:00
< / div >
< / div >
<!-- REVENUE CHART -->
< div class = "chart-card" >
2026-07-07 07:11:50 +00:00
< h3 > < svg width = "16" height = "16" viewBox = "0 0 16 16" fill = "none" > < path d = "M2 12L6 8L9 11L14 5" stroke = "#22c55e" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" / > < path d = "M10 5H14V9" stroke = "#22c55e" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" / > < / svg > Intjänat per månad (2026)< / h3 >
2026-07-05 06:41:32 +00:00
< div class = "bar-chart" id = "revenueChart" > < / div >
< / div >
<!-- STRIPE CONNECT SUMMARY -->
< div style = "display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;margin-bottom:20px" >
2026-07-07 07:11:50 +00:00
< div style = "background:var(--card);border:1px solid var(--border);border-radius: var(--radius-md);padding:18px" >
2026-07-05 06:41:32 +00:00
< div style = "display:flex;align-items:center;gap:8px;margin-bottom:12px" >
2026-07-07 07:11:50 +00:00
< div style = "width:32px;height:32px;background:#635BFF;border-radius: var(--radius-md);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px" > $< / div >
2026-07-05 06:41:32 +00:00
< div >
< div style = "font-size:12px;font-weight:700" > Stripe Connect< / div >
< div style = "font-size:11px;color:var(--text3)" > Betalningsplattform< / div >
< / div >
< / div >
< div style = "font-size:11px;color:var(--text2)" > Anslutna konton: < strong id = "stripeConnected" > 0< / strong > < / div >
< div style = "font-size:11px;color:var(--text2)" > Under granskning: < strong id = "stripeReview" > 0< / strong > < / div >
2026-07-07 07:11:50 +00:00
< div style = "margin-top:10px" > < span class = "badge badge-green" > Live< / span > < / div >
2026-07-05 06:41:32 +00:00
< / div >
2026-07-07 07:11:50 +00:00
< div style = "background:var(--card);border:1px solid var(--border);border-radius: var(--radius-md);padding:18px" >
< div style = "font-size:12px;font-weight:700;margin-bottom:12px" > Utbetalningsstatus< / div >
2026-07-05 06:41:32 +00:00
< div id = "payoutStatusChart" > < / div >
< / div >
2026-07-07 07:11:50 +00:00
< div style = "background:var(--card);border:1px solid var(--border);border-radius: var(--radius-md);padding:18px" >
< div style = "font-size:12px;font-weight:700;margin-bottom:12px" > Genomsnittlig rating< / div >
2026-07-05 06:41:32 +00:00
< div style = "font-size:36px;font-weight:900;letter-spacing:-.02em;color:var(--yellow)" id = "avgRating" > 4.7< / div >
< div style = "font-size:11px;color:var(--text2)" > Baserat på < span id = "ratingCount" > 0< / span > omdömen< / div >
2026-07-07 07:11:50 +00:00
< div style = "margin-top:8px;font-size:18px" > < / div >
2026-07-05 06:41:32 +00:00
< / div >
< / div >
< / div >
<!-- PER ZOOMER -->
< div class = "view" id = "view-zoomers" >
2026-07-07 07:11:50 +00:00
< div class = "table-header" style = "background:var(--card);border:1px solid var(--border);border-radius: var(--radius-md) 12px 0 0;padding:14px 20px;margin-bottom:0" >
< h3 > Per zoomer< / h3 >
2026-07-05 06:41:32 +00:00
< div class = "table-header-actions" >
2026-07-07 07:11:50 +00:00
< input type = "text" class = "btn btn-secondary btn-sm" placeholder = "<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 > Sök zoomer..." id="zoomerSearch" oninput="filterZoomers()" style="min-width:200px;text-align:left">
2026-07-05 06:41:32 +00:00
< select class = "btn btn-secondary btn-sm" id = "zoomerStatusFilter" onchange = "filterZoomers()" style = "border:1px solid var(--border)" >
< option value = "" > Alla< / option >
< option value = "active" > Aktiva< / option >
< option value = "inactive" > Inaktiva< / option >
< / select >
< / div >
< / div >
2026-07-07 07:11:50 +00:00
< div class = "tbl-scroll" style = "background:var(--card);border:1px solid var(--border);border-top:none;border-radius: 0 0 12px 12px;margin-bottom:20px" >
2026-07-05 06:41:32 +00:00
< table >
< thead >
< tr > < th > Zoomer< / th > < th > Land< / th > < th class = "right" > Uppdrag< / th > < th class = "right" > Intjänat< / th > < th class = "right" > Rating< / th > < th > Status< / th > < th > Utbetalning< / th > < th > Åtgärd< / th > < / tr >
< / thead >
< tbody id = "zoomerTable" > < / tbody >
< / table >
< / div >
< / div >
<!-- PAYOUTS -->
< div class = "view" id = "view-payouts" >
< div class = "table-wrap" >
< div class = "table-header" >
2026-07-07 07:11:50 +00:00
< h3 > Utbetalningshistorik< / h3 >
2026-07-05 06:41:32 +00:00
< div class = "table-header-actions" >
< select class = "btn btn-secondary btn-sm" id = "payoutStatusFilter2" onchange = "filterPayouts()" style = "border:1px solid var(--border)" >
< option value = "" > Alla statusar< / option >
< option value = "pending" > Pending< / option >
< option value = "sent" > Sent< / option >
< option value = "failed" > Failed< / option >
< / select >
< / div >
< / div >
< div class = "tbl-scroll" >
< table >
< thead > < tr > < th > Datum< / th > < th > Zoomer< / th > < th class = "right" > Belopp< / th > < th > Status< / th > < th > Stripe ID< / th > < th > Åtgärd< / th > < / tr > < / thead >
< tbody id = "payoutTable" > < / tbody >
< / table >
< / div >
< / div >
< / div >
<!-- GEO -->
< div class = "view" id = "view-geo" >
2026-07-07 07:11:50 +00:00
< div class = "section-title" > Geografisk fördelning< / div >
2026-07-05 06:41:32 +00:00
< div class = "geo-grid" id = "geoGrid" > < / div >
< / div >
<!-- DAC7 -->
< div class = "view" id = "view-dac7" >
< div class = "dac7-panel" >
< div style = "display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:20px" >
< div >
2026-07-07 07:11:50 +00:00
< h3 style = "font-size:15px;font-weight:700;margin-bottom:4px" > DAC7 Compliance< / h3 >
2026-07-05 06:41:32 +00:00
< p style = "font-size:12px;color:var(--text2)" > EU-direktiv 2021/514 — rapportering av plattformsekonomin< / p >
< / div >
2026-07-07 07:11:50 +00:00
< button class = "btn btn-primary btn-sm" > Exportera DAC7-rapport< / button >
2026-07-05 06:41:32 +00:00
< / div >
< div style = "display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:20px" >
< div style = "text-align:center" >
< div style = "font-size:12px;font-weight:700;color:var(--text2);margin-bottom:8px" > Verifierade TIN< / div >
< div style = "font-size:42px;font-weight:900;color:var(--green)" id = "dac7Pct" > 0%< / div >
< div class = "compliance-meter" > < div class = "compliance-fill" id = "dac7Bar" style = "width:0%" > < / div > < / div >
< div style = "font-size:11px;color:var(--text3)" > < span id = "dac7Verified" > 0< / span > av < span id = "dac7Total" > 0< / span > zoomers< / div >
< / div >
< div >
< div style = "font-size:12px;font-weight:700;color:var(--text2);margin-bottom:12px" > Status per kategori< / div >
< div id = "dac7StatusList" > < / div >
< / div >
< / div >
2026-07-07 07:11:50 +00:00
< div style = "background:#fffbeb;border:1px solid #fde68a;border-radius: var(--radius-md);padding:14px;font-size:12px;color:#854d0e" >
< strong > < svg width = "20" height = "20" viewBox = "0 0 20 20" fill = "none" > < path d = "M10 3L17 17H3L10 3Z" stroke = "#f59e0b" stroke-width = "2" / > < path d = "M10 8V12M10 14V14.5" stroke = "#f59e0b" stroke-width = "2" stroke-linecap = "round" / > < / svg > DAC7 deadline:< / strong > Rapportering för kalenderår 2026 ska lämnas till Skatteverket senast < strong > 31 januari 2027< / strong > . Zoomers med intäkter > 2 000 EUR eller > 25 transaktioner måste ha verifierat TIN/PNR.
2026-07-05 06:41:32 +00:00
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< script >
2026-07-07 07:11:50 +00:00
// DATA
2026-07-05 06:41:32 +00:00
const zoomers = [ ] ;
const payoutHistory = [ ] ;
{ date : '2026-05-15' , zoomer : 'Pedro Santos' , amount : 2800 , status : 'failed' , stripeId : 'po_3Px9f2026051502' } ,
{ date : '2026-06-01' , zoomer : 'Sofia Bergman' , amount : 13200 , status : 'pending' , stripeId : '—' } ,
{ date : '2026-06-01' , zoomer : 'Erik Johansson' , amount : 8900 , status : 'pending' , stripeId : '—' } ,
{ date : '2026-06-01' , zoomer : 'Emma Strand' , amount : 10400 , status : 'pending' , stripeId : '—' } ,
{ date : '2026-06-01' , zoomer : 'Hans Weber' , amount : 11300 , status : 'pending' , stripeId : '—' } ,
{ date : '2026-06-01' , zoomer : 'Jens Andersen' , amount : 5200 , status : 'pending' , stripeId : '—' } ,
{ date : '2026-06-01' , zoomer : 'Piet van Dijk' , amount : 4200 , status : 'pending' , stripeId : '—' } ,
{ date : '2026-06-01' , zoomer : 'Anneliese de Boer' , amount : 6100 , status : 'pending' , stripeId : '—' } ,
] ;
const monthlyRevenue = [
{ month : 'Jan' , val : 28400 } , { month : 'Feb' , val : 34200 } , { month : 'Mar' , val : 41800 } ,
{ month : 'Apr' , val : 57600 } , { month : 'Maj' , val : 82300 } , { month : 'Jun' , val : 0 } ,
{ month : 'Jul' , val : 0 } , { month : 'Aug' , val : 0 } , { month : 'Sep' , val : 0 } ,
{ month : 'Okt' , val : 0 } , { month : 'Nov' , val : 0 } , { month : 'Dec' , val : 0 } ,
] ;
const geoData = [
2026-07-07 07:11:50 +00:00
{ country : 'Sverige' , flag : '<span class="flag-se">SE</span>' , zoomers : 5 , earned : 142000 , pct : 52 } ,
{ country : 'Norge' , flag : '' , zoomers : 2 , earned : 52300 , pct : 19 } ,
{ country : 'Danmark' , flag : '' , zoomers : 2 , earned : 41100 , pct : 15 } ,
{ country : 'Tyskland' , flag : '' , zoomers : 3 , earned : 66600 , pct : 24 } ,
{ country : 'Nederländerna' , flag : '' , zoomers : 2 , earned : 31200 , pct : 11 } ,
{ country : 'Portugal' , flag : '' , zoomers : 1 , earned : 8500 , pct : 3 } ,
2026-07-05 06:41:32 +00:00
] ;
2026-07-07 07:11:50 +00:00
// COMPUTED
2026-07-05 06:41:32 +00:00
const totalEarned = zoomers . reduce ( ( s , z ) => s + z . earned , 0 ) ;
const totalAssignments = zoomers . reduce ( ( s , z ) => s + z . assignments , 0 ) ;
const activeCount = zoomers . filter ( z => z . status === 'active' ) . length ;
const avgEarned = Math . round ( totalEarned / zoomers . length ) ;
const pendingPayout = payoutHistory . filter ( p => p . status === 'pending' ) . reduce ( ( s , p ) => s + p . amount , 0 ) ;
const tinVerified = zoomers . filter ( z => z . tin ) . length ;
const dac7Pct = Math . round ( tinVerified / zoomers . length * 100 ) ;
const avgRating = ( zoomers . reduce ( ( s , z ) => s + z . rating , 0 ) / zoomers . length ) . toFixed ( 1 ) ;
const totalRatingCount = zoomers . reduce ( ( s , z ) => s + z . assignments , 0 ) ;
2026-07-07 07:11:50 +00:00
// RENDER ALL
2026-07-05 06:41:32 +00:00
function renderAll ( ) {
// Stats
document . getElementById ( 'statTotal' ) . textContent = fmtK ( totalEarned ) + ' kr' ;
document . getElementById ( 'statTotalChange' ) . textContent = '+34% vs förra månaden' ;
document . getElementById ( 'statActive' ) . textContent = activeCount ;
document . getElementById ( 'statAssignments' ) . textContent = totalAssignments ;
document . getElementById ( 'statAvg' ) . textContent = fmtK ( avgEarned ) + ' kr' ;
document . getElementById ( 'statPending' ) . textContent = fmtK ( pendingPayout ) + ' kr' ;
document . getElementById ( 'statDac7' ) . textContent = dac7Pct + '%' ;
document . getElementById ( 'statDac7' ) . style . color = dac7Pct >= 80 ? 'var(--green)' : dac7Pct >= 60 ? 'var(--yellow)' : 'var(--red)' ;
document . getElementById ( 'pcAmount' ) . textContent = fmt ( pendingPayout ) + ' kr' ;
document . getElementById ( 'pcCount' ) . textContent = payoutHistory . filter ( p => p . status === 'pending' ) . length + ' zoomers' ;
document . getElementById ( 'stripeConnected' ) . textContent = zoomers . filter ( z => z . tin ) . length ;
document . getElementById ( 'stripeReview' ) . textContent = zoomers . filter ( z => ! z . tin ) . length ;
document . getElementById ( 'avgRating' ) . textContent = avgRating ;
document . getElementById ( 'ratingCount' ) . textContent = totalRatingCount ;
renderChart ( ) ;
renderPayoutStatusChart ( ) ;
renderZoomerTable ( zoomers ) ;
renderPayoutTable ( payoutHistory ) ;
renderGeo ( ) ;
renderDAC7 ( ) ;
}
function renderChart ( ) {
const maxVal = Math . max ( ... monthlyRevenue . map ( m => m . val ) , 1 ) ;
document . getElementById ( 'revenueChart' ) . innerHTML = monthlyRevenue . map ( m => `
<div class="bar-wrap">
<div class="bar-value"> ${ m . val ? fmtK ( m . val ) + 'k' : '' } </div>
<div class="bar" style="height: ${ m . val ? Math . max ( 8 , m . val / maxVal * 100 ) : 4 } px; ${ ! m . val ? 'background:var(--surface);opacity:1' : '' } "></div>
<div class="bar-label"> ${ m . month } </div>
</div> ` ) . join ( '' ) ;
}
function renderPayoutStatusChart ( ) {
const sent = payoutHistory . filter ( p => p . status === 'sent' ) . length ;
const pending = payoutHistory . filter ( p => p . status === 'pending' ) . length ;
const failed = payoutHistory . filter ( p => p . status === 'failed' ) . length ;
document . getElementById ( 'payoutStatusChart' ) . innerHTML = `
<div style="display:flex;flex-direction:column;gap:8px">
2026-07-07 07:11:50 +00:00
<div style="display:flex;justify-content:space-between;align-items:center"><span class="badge badge-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> Sent</span><strong> ${ sent } </strong></div>
2026-07-05 06:41:32 +00:00
<div style="display:flex;justify-content:space-between;align-items:center"><span class="badge badge-yellow">⏳ Pending</span><strong> ${ pending } </strong></div>
2026-07-07 07:11:50 +00:00
<div style="display:flex;justify-content:space-between;align-items:center"><span class="badge badge-red"> Failed</span><strong> ${ failed } </strong></div>
2026-07-05 06:41:32 +00:00
</div> ` ;
}
function renderZoomerTable ( data ) {
document . getElementById ( 'zoomerTable' ) . innerHTML = data . map ( z => `
<tr>
<td>
<div style="display:flex;align-items:center;gap:10px">
2026-07-07 07:11:50 +00:00
<div style="width:32px;height:32px;border-radius: var(--radius-full);background:linear-gradient(135deg,var(--qx),var(--purple));display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:12px;flex-shrink:0"> ${ z . name . split ( ' ' ) . map ( n => n [ 0 ] ) . join ( '' ) } </div>
2026-07-05 06:41:32 +00:00
<div>
<div style="font-weight:700;font-size:13px"> ${ z . name } </div>
<div style="font-size:11px;color:var(--text3)"> ${ z . email } </div>
</div>
</div>
</td>
<td> ${ z . flag } ${ z . country } </td>
<td class="right"> ${ z . assignments } </td>
<td class="right mono"><strong> ${ fmt ( z . earned ) } kr</strong></td>
2026-07-07 07:11:50 +00:00
<td class="right"> ${ z . rating } </td>
<td><span class="badge ${ z . status === 'active' ? 'badge-green' : 'badge-gray' } "> ${ z . status === 'active' ? ' Aktiv' : ' Inaktiv' } </span></td>
2026-07-05 06:41:32 +00:00
<td><span class="badge ${ payoutBadge ( z . payout ) } "> ${ payoutLabel ( z . payout ) } </span></td>
<td><button class="btn btn-xs btn-secondary">Detaljer</button></td>
</tr> ` ) . join ( '' ) ;
}
function filterZoomers ( ) {
const q = document . getElementById ( 'zoomerSearch' ) . value . toLowerCase ( ) ;
const s = document . getElementById ( 'zoomerStatusFilter' ) . value ;
let filtered = zoomers . filter ( z => {
if ( q && ! z . name . toLowerCase ( ) . includes ( q ) ) return false ;
if ( s && z . status !== s ) return false ;
return true ;
} ) ;
renderZoomerTable ( filtered ) ;
}
function renderPayoutTable ( data ) {
const sf = document . getElementById ( 'payoutStatusFilter2' ) ? . value || '' ;
const filtered = sf ? data . filter ( p => p . status === sf ) : data ;
document . getElementById ( 'payoutTable' ) . innerHTML = filtered . map ( p => `
<tr>
<td style="font-size:12px;color:var(--text3)"> ${ p . date } </td>
<td><strong> ${ p . zoomer } </strong></td>
<td class="right mono"><strong> ${ fmt ( p . amount ) } kr</strong></td>
<td><span class="badge ${ payoutBadge ( p . status ) } "> ${ payoutLabel ( p . status ) } </span></td>
<td style="font-size:11px;font-family:monospace;color:var(--text3)"> ${ p . stripeId } </td>
<td> ${ p . status === 'failed' ? ` <button class="btn btn-xs btn-danger" onclick="alert('Retry initiated for ${ p . zoomer } ')">↩ Retry</button> ` : ` <button class="btn btn-xs btn-secondary">Detaljer</button> ` } </td>
</tr> ` ) . join ( '' ) ;
}
function filterPayouts ( ) { renderPayoutTable ( payoutHistory ) ; }
function renderGeo ( ) {
const total = geoData . reduce ( ( s , g ) => s + g . earned , 0 ) ;
document . getElementById ( 'geoGrid' ) . innerHTML = geoData . map ( g => `
<div class="geo-card">
<div class="gc-name"> ${ g . flag } ${ g . country } </div>
<div class="geo-bar-wrap"><div class="geo-bar" style="width: ${ g . pct } %"></div></div>
<div class="geo-meta"><strong> ${ g . zoomers } zoomers</strong> · ${ fmtK ( g . earned ) } kr · ${ g . pct } % av total</div>
</div> ` ) . join ( '' ) ;
}
function renderDAC7 ( ) {
document . getElementById ( 'dac7Pct' ) . textContent = dac7Pct + '%' ;
document . getElementById ( 'dac7Bar' ) . style . width = dac7Pct + '%' ;
document . getElementById ( 'dac7Bar' ) . style . background = dac7Pct >= 80 ? 'var(--green)' : dac7Pct >= 60 ? 'var(--yellow)' : 'var(--red)' ;
document . getElementById ( 'dac7Verified' ) . textContent = tinVerified ;
document . getElementById ( 'dac7Total' ) . textContent = zoomers . length ;
document . getElementById ( 'dac7StatusList' ) . innerHTML = `
<div style="display:flex;flex-direction:column;gap:8px">
2026-07-07 07:11:50 +00:00
<div style="display:flex;justify-content:space-between"><span style="font-size:12px;color:var(--text2)"><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> TIN verifierat</span><strong style="color:var(--green)"> ${ tinVerified } </strong></div>
2026-07-05 06:41:32 +00:00
<div style="display:flex;justify-content:space-between"><span style="font-size:12px;color:var(--text2)">⏳ Saknar TIN</span><strong style="color:var(--red)"> ${ zoomers . length - tinVerified } </strong></div>
2026-07-07 07:11:50 +00:00
<div style="display:flex;justify-content:space-between"><span style="font-size:12px;color:var(--text2)"><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> Rapporteringspliktiga</span><strong> ${ zoomers . filter ( z => z . earned > 25000 || z . assignments > 25 ) . length } </strong></div>
<div style="display:flex;justify-content:space-between"><span style="font-size:12px;color:var(--text2)"> Under tröskel</span><strong style="color:var(--text3)"> ${ zoomers . filter ( z => z . earned <= 25000 && z . assignments <= 25 ) . length } </strong></div>
2026-07-05 06:41:32 +00:00
</div> ` ;
}
2026-07-07 07:11:50 +00:00
// UTILS
2026-07-05 06:41:32 +00:00
function payoutBadge ( s ) { return { sent : 'badge-green' , pending : 'badge-yellow' , failed : 'badge-red' } [ s ] || 'badge-gray' ; }
2026-07-07 07:11:50 +00:00
function payoutLabel ( s ) { return { sent : '<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> Sent' , pending : '⏳ Pending' , failed : ' Failed' } [ s ] || s ; }
2026-07-05 06:41:32 +00:00
function fmt ( n ) { return Math . round ( n ) . toLocaleString ( 'sv-SE' ) ; }
function fmtK ( n ) { return n >= 1000000 ? ( n / 1000000 ) . toFixed ( 1 ) + 'M' : n >= 1000 ? Math . round ( n / 1000 ) + 'k' : Math . round ( n ) . toString ( ) ; }
function switchTab ( id , el ) {
document . querySelectorAll ( '.view' ) . forEach ( v => v . classList . remove ( 'active' ) ) ;
document . getElementById ( 'view-' + id ) . classList . add ( 'active' ) ;
document . querySelectorAll ( '.tab' ) . forEach ( t => t . classList . remove ( 'active' ) ) ;
el . classList . add ( 'active' ) ;
}
function exportCSV ( ) {
let csv = 'Zoomer,Land,Uppdrag,Intjänat,Rating,Status,Utbetalning,TIN\n' ;
zoomers . forEach ( z => {
csv += ` " ${ z . name } "," ${ z . country } ", ${ z . assignments } , ${ z . earned } , ${ z . rating } ," ${ z . status } "," ${ z . payout } "," ${ z . tin ? 'Ja' : 'Nej' } " \n ` ;
} ) ;
const blob = new Blob ( [ csv ] , { type : 'text/csv;charset=utf-8;' } ) ;
const a = document . createElement ( 'a' ) ;
a . href = URL . createObjectURL ( blob ) ;
a . download = 'quixzoom-revenue-' + new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] + '.csv' ;
a . click ( ) ;
}
renderAll ( ) ;
< / script >
< / body >
< / html >