User Management

User Email Role Teams

Loading...

// ═══════════════════════════════════════════════════════════ // GMV Max Panel // ═══════════════════════════════════════════════════════════ var gmvState = { storeId: '', accountId: '', stores: [], campaigns: [], sortCol: 'cost', sortDir: 'desc', dateStart: '', dateEnd: '', expandedCampaigns: {}, videoCache: {} }; var gmvCols = [ {key:'campaignName', label:'Campaign', fmt:'text', w:'220px'}, {key:'cost', label:'Cost', fmt:'dollar', w:'90px'}, {key:'grossRevenue', label:'Revenue', fmt:'dollar', w:'100px'}, {key:'orders', label:'Orders', fmt:'int', w:'70px'}, {key:'roi', label:'ROI', fmt:'roi', w:'65px'}, {key:'costPerOrder', label:'CPO', fmt:'dollar', w:'80px'}, {key:'aov', label:'AOV', fmt:'dollar', w:'80px'} ]; // ── Date helpers ── function gmvFmtDate(d) { return d.toISOString().slice(0,10); } function gmvSetDatePreset(p) { var today = new Date(), s, e; e = new Date(today); if (p==='today') { s = new Date(today); } else if (p==='yesterday') { s = new Date(today); s.setDate(s.getDate()-1); e = new Date(s); } else if (p==='7d') { s = new Date(today); s.setDate(s.getDate()-6); } else if (p==='14d') { s = new Date(today); s.setDate(s.getDate()-13); } else if (p==='30d') { s = new Date(today); s.setDate(s.getDate()-29); } else if (p==='month'){ s = new Date(today.getFullYear(), today.getMonth(), 1); } gmvState.dateStart = gmvFmtDate(s); gmvState.dateEnd = gmvFmtDate(e); document.getElementById('gmvDateStart').value = gmvState.dateStart; document.getElementById('gmvDateEnd').value = gmvState.dateEnd; document.getElementById('gmvDateStartLabel').textContent = gmvState.dateStart; document.getElementById('gmvDateEndLabel').textContent = gmvState.dateEnd; document.querySelectorAll('#gmvDatePresets .ma-preset-pill').forEach(function(el) { el.classList.toggle('active', el.textContent.toLowerCase().replace(/\s/g,'').indexOf(p) >= 0 || (p==='7d' && el.textContent==='Last 7d')); }); if (gmvState.storeId) fetchGmvMax(); } // ── Date picker ── var gmvTgState = { mode: 'start', month: new Date().getMonth(), year: new Date().getFullYear(), anchor: null }; function gmvTgOpenPicker(mode, el) { gmvTgState.mode = mode; gmvTgState.anchor = el; var cur = mode==='start' ? gmvState.dateStart : gmvState.dateEnd; if (cur) { var d = new Date(cur+'T00:00:00'); gmvTgState.month = d.getMonth(); gmvTgState.year = d.getFullYear(); } gmvTgRender(); var picker = document.getElementById('gmvTgPicker'); var rect = el.getBoundingClientRect(); picker.style.top = (rect.bottom + 6) + 'px'; picker.style.left = rect.left + 'px'; picker.classList.add('open'); document.getElementById('gmvTgPickerOverlay').classList.add('open'); } function gmvTgClosePicker() { document.getElementById('gmvTgPicker').classList.remove('open'); document.getElementById('gmvTgPickerOverlay').classList.remove('open'); } function gmvTgNavMonth(dir) { gmvTgState.month += dir; if (gmvTgState.month<0){gmvTgState.month=11;gmvTgState.year--;} if (gmvTgState.month>11){gmvTgState.month=0;gmvTgState.year++;} gmvTgRender(); } function gmvTgRender() { var m = gmvTgState.month, y = gmvTgState.year; document.getElementById('gmvTgMonthLabel').textContent = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][m] + ' ' + y; var first = new Date(y,m,1).getDay(), days = new Date(y,m+1,0).getDate(); var sel = gmvTgState.mode==='start' ? gmvState.dateStart : gmvState.dateEnd; var h = ''; for (var i=0;i'+d+''; } document.getElementById('gmvTgDays').innerHTML = h; } function gmvTgPick(ds) { if (gmvTgState.mode==='start') { gmvState.dateStart=ds; document.getElementById('gmvDateStart').value=ds; document.getElementById('gmvDateStartLabel').textContent=ds; } else { gmvState.dateEnd=ds; document.getElementById('gmvDateEnd').value=ds; document.getElementById('gmvDateEndLabel').textContent=ds; } gmvTgClosePicker(); } function gmvTgClear() { if (gmvTgState.mode==='start') { gmvState.dateStart=''; document.getElementById('gmvDateStart').value=''; document.getElementById('gmvDateStartLabel').textContent='Start date'; } else { gmvState.dateEnd=''; document.getElementById('gmvDateEnd').value=''; document.getElementById('gmvDateEndLabel').textContent='End date'; } gmvTgClosePicker(); } function gmvTgToday() { gmvTgPick(gmvFmtDate(new Date())); } // ── Format value ── function gmvFmtVal(v, fmt) { if (fmt==='dollar') return '$' + (v||0).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2}); if (fmt==='int') return (v||0).toLocaleString(); if (fmt==='roi') return (v||0).toFixed(2)+'x'; return v || ''; } // ── Load panel ── async function loadGmvMaxPanel() { try { var resp = await fetch(API_BASE + '/tiktok-gmv/all-stores', { headers: authHeaders() }); if (!resp.ok) { document.getElementById('gmvError').style.display = ''; document.getElementById('gmvError').textContent = 'Failed to load GMV Max stores. Check Connections.'; return; } var data = await resp.json(); var stores = (data.gmv_available || []).filter(function(s) { return s.is_gmv_max_available; }); gmvState.stores = stores; var sel = document.getElementById('gmvStoreSelect'); sel.innerHTML = ''; stores.forEach(function(s) { var regions = (s.regions || []).join(', '); var label = (s.store_name || s.store_id) + (regions ? ' [' + regions + ']' : ''); var advId = s.exclusive_advertiser_id || ''; sel.innerHTML += ''; }); if (stores.length === 1) { sel.selectedIndex = 1; gmvState.storeId = stores[0].store_id; gmvState.accountId = stores[0].exclusive_advertiser_id || ''; } document.getElementById('gmvError').style.display = 'none'; if (!gmvState.dateStart) gmvSetDatePreset('7d'); } catch(e) { document.getElementById('gmvError').style.display = ''; document.getElementById('gmvError').textContent = 'Failed to load stores: ' + e.message; } } // ── Fetch data ── async function fetchGmvMax() { if (!gmvState.storeId) { alert('Please select a store'); return; } if (!gmvState.dateStart || !gmvState.dateEnd) { alert('Please select date range'); return; } document.getElementById('gmvLoading').style.display = ''; document.getElementById('gmvError').style.display = 'none'; document.getElementById('gmvTableWrap').style.display = 'none'; document.getElementById('gmvSummaryRow').style.display = 'none'; gmvState.expandedCampaigns = {}; gmvState.videoCache = {}; try { var url = API_BASE + '/tiktok-gmv/store-report?store_id=' + gmvState.storeId + '&start=' + gmvState.dateStart + '&end=' + gmvState.dateEnd; if (gmvState.accountId) url += '&account_id=' + gmvState.accountId; var resp = await fetch(url, { headers: authHeaders() }); if (!resp.ok) throw new Error((await resp.json().catch(function(){return {};}) ).detail || 'API error'); var data = await resp.json(); gmvState.campaigns = data.campaigns || []; gmvState.summary = data.summary || {}; gmvState.accountId = data.account_id || gmvState.accountId; document.getElementById('gmvLoading').style.display = 'none'; renderGmvSummary(); renderGmvTable(); } catch(e) { document.getElementById('gmvLoading').style.display = 'none'; document.getElementById('gmvError').style.display = ''; document.getElementById('gmvError').textContent = e.message; } } // ── Summary cards ── function renderGmvSummary() { var s = gmvState.summary; if (!s || !s.current) return; var cur = s.current, chg = s.changes || {}; function badge(val) { if (val === null || val === undefined) return ''; var color = val >= 0 ? '#22c55e' : '#ef4444'; var arrow = val >= 0 ? '\u2191' : '\u2193'; return '
'+arrow+Math.abs(val).toFixed(1)+'%
'; } var cards = [ {label:'Cost', value:'$'+(cur.cost||0).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2}), change:badge(chg.cost)}, {label:'Revenue', value:'$'+(cur.grossRevenue||0).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2}), change:badge(chg.grossRevenue)}, {label:'Orders', value:(cur.orders||0).toLocaleString(), change:badge(chg.orders)}, {label:'ROI', value:(cur.roi||0).toFixed(2)+'x', change:badge(chg.roi)}, {label:'CPO', value:'$'+(cur.costPerOrder||0).toFixed(2), change:badge(chg.costPerOrder)}, {label:'AOV', value:'$'+(cur.aov||0).toFixed(2), change:badge(chg.aov)} ]; var el = document.getElementById('gmvSummaryRow'); el.style.display = 'grid'; el.innerHTML = cards.map(function(c) { return '
'+c.label+'
'+c.value+'
'+c.change+'
'; }).join(''); } // ── Table rendering ── function renderGmvTable() { var rows = gmvState.campaigns.slice(); // Sort rows.sort(function(a,b) { var va = a[gmvState.sortCol]||0, vb = b[gmvState.sortCol]||0; if (typeof va === 'string') va = va.toLowerCase(); if (typeof vb === 'string') vb = vb.toLowerCase(); if (va < vb) return gmvState.sortDir==='asc' ? -1 : 1; if (va > vb) return gmvState.sortDir==='asc' ? 1 : -1; return 0; }); // Head var headHtml = ''; gmvCols.forEach(function(c) { var arrow = gmvState.sortCol===c.key ? (gmvState.sortDir==='asc'?'↑':'↓') : ''; headHtml += ''+c.label+' '+arrow+''; }); headHtml += ''; document.getElementById('gmvTableHead').innerHTML = headHtml; // Body var bodyHtml = ''; rows.forEach(function(r) { var cid = r.campaignId || ''; var expanded = gmvState.expandedCampaigns[cid]; var chevron = expanded ? '\u25BC' : '\u25B6'; bodyHtml += ''; bodyHtml += ''+chevron+''; gmvCols.forEach(function(c) { if (c.key==='campaignName') { var name = r.campaignName || r.campaignId || 'N/A'; bodyHtml += ''+name+''; } else { bodyHtml += ''+gmvFmtVal(r[c.key], c.fmt)+''; } }); bodyHtml += ''; // Expanded videos row if (expanded) { var videos = gmvState.videoCache[cid]; bodyHtml += ''; if (!videos) { bodyHtml += '
Loading top videos...
'; } else if (videos.length === 0) { bodyHtml += '
No video data available
'; } else { bodyHtml += ''; bodyHtml += ''; videos.forEach(function(v) { bodyHtml += ''; bodyHtml += ''; bodyHtml += ''; bodyHtml += ''; bodyHtml += ''; bodyHtml += ''; bodyHtml += ''; bodyHtml += ''; }); bodyHtml += '
Item IDCostOrdersRevenueROICPO
'+v.itemId+''+gmvFmtVal(v.cost,'dollar')+''+gmvFmtVal(v.orders,'int')+''+gmvFmtVal(v.grossRevenue,'dollar')+''+gmvFmtVal(v.roi,'roi')+''+gmvFmtVal(v.costPerOrder,'dollar')+'
'; } bodyHtml += ''; } }); document.getElementById('gmvTableBody').innerHTML = bodyHtml; // Foot — totals if (rows.length > 0) { var totals = {cost:0, grossRevenue:0, orders:0}; rows.forEach(function(r) { totals.cost += r.cost||0; totals.grossRevenue += r.grossRevenue||0; totals.orders += r.orders||0; }); totals.roi = totals.cost > 0 ? totals.grossRevenue/totals.cost : 0; totals.costPerOrder = totals.orders > 0 ? totals.cost/totals.orders : 0; totals.aov = totals.orders > 0 ? totals.grossRevenue/totals.orders : 0; var footHtml = ''; gmvCols.forEach(function(c) { if (c.key==='campaignName') footHtml += 'Total ('+rows.length+' campaigns)'; else footHtml += ''+gmvFmtVal(totals[c.key], c.fmt)+''; }); footHtml += ''; document.getElementById('gmvTableFoot').innerHTML = footHtml; } document.getElementById('gmvTableWrap').style.display = ''; } function gmvSortBy(col) { if (gmvState.sortCol === col) gmvState.sortDir = gmvState.sortDir==='asc'?'desc':'asc'; else { gmvState.sortCol = col; gmvState.sortDir = 'desc'; } renderGmvTable(); } // ── Toggle campaign expansion ── async function toggleGmvCampaign(campaignId) { if (gmvState.expandedCampaigns[campaignId]) { delete gmvState.expandedCampaigns[campaignId]; renderGmvTable(); return; } gmvState.expandedCampaigns[campaignId] = true; renderGmvTable(); // show loading state if (gmvState.videoCache[campaignId]) { renderGmvTable(); return; } try { var url = API_BASE + '/tiktok-gmv/campaign-videos?store_id=' + gmvState.storeId + '&campaign_id=' + campaignId + '&start=' + gmvState.dateStart + '&end=' + gmvState.dateEnd; if (gmvState.accountId) url += '&account_id=' + gmvState.accountId; var resp = await fetch(url, { headers: authHeaders() }); if (!resp.ok) throw new Error('Failed to load videos'); var data = await resp.json(); gmvState.videoCache[campaignId] = data.items || []; } catch(e) { gmvState.videoCache[campaignId] = []; console.error('Failed to load campaign videos:', e); } renderGmvTable(); }