`); w.document.close(); w.print(); w.close(); } // ── Activity Log ────────────────────────────────────────────────────────────── async function loadLog() { const search = document.getElementById('log-search').value.toLowerCase(); const atype = document.getElementById('log-filter').value; const from = document.getElementById('log-from').value; const to = document.getElementById('log-to').value; let q = db.from('activity_log').select('*').order('id',{ascending:false}).limit(300); if (atype) q = q.eq('action_type',atype); if (from) q = q.gte('created_at',from); if (to) q = q.lte('created_at',to+'T23:59:59'); const { data } = await q; let rows = data||[]; if (search) rows = rows.filter(r=>Object.values(r).join(' ').toLowerCase().includes(search)); document.getElementById('log-container').innerHTML = rows.length ? rows.map(renderLogEntry).join('') : '
No log entries found
'; } // ── Reports ─────────────────────────────────────────────────────────────────── function loadReports() { if (!document.getElementById('rpt-from').value) { const to = new Date().toISOString().split('T')[0]; const from = new Date(Date.now()-30*864e5).toISOString().split('T')[0]; document.getElementById('rpt-from').value = from; document.getElementById('rpt-to').value = to; } runReport(); } function selectReport(name, btn) { _currentReport = name; document.querySelectorAll('#meter-app .rpt-tab-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); document.getElementById('rpt-status-wrap').style.display = name==='meters' ? '' : 'none'; document.getElementById('rpt-type-wrap').style.display = name==='meters' ? '' : 'none'; document.getElementById('rpt-tech-wrap').style.display = name==='actlog' ? '' : 'none'; document.getElementById('rpt-action-wrap').style.display = name==='actlog' ? '' : 'none'; runReport(); } const statBox = (val,label,color='var(--text)')=>`
${val}
${label}
`; const rptHdr = (title,sub)=>``; async function runReport() { const out = document.getElementById('rpt-output'); out.innerHTML='
Loading...
'; const from = document.getElementById('rpt-from').value; const to = document.getElementById('rpt-to').value; const status = document.getElementById('rpt-status').value; const mtype = document.getElementById('rpt-mtype').value; const tech = document.getElementById('rpt-tech').value; const action = document.getElementById('rpt-action').value; let data; if (_currentReport==='meters') { let q = db.from('meters').select('*').order('status').order('manufacturer').order('meter_number'); if (status) q=q.eq('status',status); if (mtype) q=q.eq('type',mtype); const {data:rows}=await q; const summary={total:rows.length,by_status:{},by_type:{},by_manufacturer:{}}; (rows||[]).forEach(r=>{ summary.by_status[r.status]=(summary.by_status[r.status]||0)+1; summary.by_type[r.type||'?']=(summary.by_type[r.type||'?']||0)+1; summary.by_manufacturer[r.manufacturer||'?']=(summary.by_manufacturer[r.manufacturer||'?']||0)+1; }); data={rows,summary}; out.innerHTML=renderMeterRpt(data); } else if (_currentReport==='installs') { let q=db.from('meters').select('*').not('install_date','is',null).gte('install_date',from).lte('install_date',to).order('install_date',{ascending:false}); const {data:rows}=await q; data={rows,date_from:from,date_to:to,total:rows.length}; out.innerHTML=renderInstallRpt(data); } else if (_currentReport==='removals') { let q=db.from('meters').select('*').not('remove_date','is',null).gte('remove_date',from).lte('remove_date',to).order('remove_date',{ascending:false}); const {data:rows}=await q; (rows||[]).forEach(r=>{ r.consumption = r.read_at_install!=null&&r.read_at_remove!=null ? Math.round(r.read_at_remove-r.read_at_install) : null; }); data={rows,date_from:from,date_to:to,total:rows.length}; out.innerHTML=renderRemovalRpt(data); } else if (_currentReport==='technicians') { let q=db.from('activity_log').select('*').gte('created_at',from).lte('created_at',to+'T23:59:59').not('technician','is',null).neq('technician','').order('created_at',{ascending:false}); const {data:detail}=await q; const byTech={}; (detail||[]).forEach(r=>{ if(!byTech[r.technician]) byTech[r.technician]={technician:r.technician,total_actions:0,installs:0,removals:0,defectives:0,updates:0,first_activity:r.created_at.slice(0,10),last_activity:r.created_at.slice(0,10)}; const t=byTech[r.technician]; t.total_actions++; t.last_activity=r.created_at.slice(0,10); if(r.action_type==='INSTALLED') t.installs++; else if(r.action_type==='REMOVED') t.removals++; else if(r.action_type==='DEFECTIVE') t.defectives++; else if(r.action_type==='UPDATED') t.updates++; }); data={summary:Object.values(byTech).sort((a,b)=>b.total_actions-a.total_actions),detail,date_from:from,date_to:to}; out.innerHTML=renderTechRpt(data); } else if (_currentReport==='actlog') { let q=db.from('activity_log').select('*').gte('created_at',from).lte('created_at',to+'T23:59:59').order('created_at',{ascending:false}).limit(500); if (action) q=q.eq('action_type',action); if (tech) q=q.eq('technician',tech); const {data:rows}=await q; const type_counts={}; (rows||[]).forEach(r=>{ type_counts[r.action_type||'OTHER']=(type_counts[r.action_type||'OTHER']||0)+1; }); const {data:techs}=await db.from('activity_log').select('technician').not('technician','is',null).neq('technician',''); const uniqueTechs=[...new Set((techs||[]).map(t=>t.technician))].sort(); const sel=document.getElementById('rpt-tech'); const cur=sel.value; sel.innerHTML=''+uniqueTechs.map(t=>`${t}`).join(''); data={rows,date_from:from,date_to:to,total:rows.length,type_counts}; out.innerHTML=renderActLogRpt(data); } _lastReportData = data; } const renderMeterRpt = d => { const s=d.summary||{}; const bs=s.by_status||{}; return rptHdr('Meter Inventory Report','All Meters') + `
${statBox(s.total||0,'Total')}${statBox(bs.Installed||0,'Installed','var(--success)')}${statBox(bs.Warehouse||0,'Warehouse','var(--accent2)')}${statBox(bs.Defective||0,'Defective','var(--danger)')}${statBox(bs.Removed||0,'Removed','var(--accent)')}
` + `
By Manufacturer
${Object.entries(s.by_manufacturer||{}).sort((a,b)=>b[1]-a[1]).map(([m,c])=>``).join('')}
ManufacturerCount%
${m}${c}${s.total?Math.round(c/s.total*100):0}%
By Meter Type
${Object.entries(s.by_type||{}).sort((a,b)=>b[1]-a[1]).map(([t,c])=>``).join('')}
TypeCount%
${t}${c}${s.total?Math.round(c/s.total*100):0}%
` + `
Detail — ${(d.rows||[]).length} records
${(d.rows||[]).map(r=>``).join('')}
Meter #ManufacturerModelTypeStatusAccount #Service AddressInstall DateTech
${r.meter_number}${r.manufacturer||'—'}${r.model||'—'}${r.type||'—'}${sbadge(r.status)}${r.account_number||'—'}${(r.service_address||'').slice(0,32)||'—'}${r.install_date||'—'}${r.technician||'—'}
`; }; const renderInstallRpt = d => rptHdr('Meter Installs Report',`${d.date_from} to ${d.date_to}`) + `
${statBox(d.total||0,'Total Installs','var(--success)')}${statBox(d.date_from,'From')}${statBox(d.date_to,'To')}${statBox([...new Set((d.rows||[]).map(r=>r.technician).filter(Boolean))].length,'Technicians')}
` + `
${(d.rows||[]).map(r=>``).join('')}
Install DateMeter #ManufacturerModelTypeAccount #Service AddressTechnicianWork OrderInstall Read
${r.install_date||'—'}${r.meter_number}${r.manufacturer||'—'}${r.model||'—'}${r.type||'—'}${r.account_number||'—'}${(r.service_address||'').slice(0,28)||'—'}${r.technician||'—'}${r.work_order||'—'}${r.read_at_install!=null?r.read_at_install+' kWh':'—'}
`; const renderRemovalRpt = d => { const totalC=(d.rows||[]).reduce((s,r)=>s+(r.consumption||0),0); return rptHdr('Meter Removals Report',`${d.date_from} to ${d.date_to}`) + `
${statBox(d.total||0,'Total Removals','var(--accent)')}${statBox(d.date_from,'From')}${statBox(d.date_to,'To')}${statBox(Math.round(totalC).toLocaleString()+' kWh','Total Consumption','var(--accent2)')}
` + `
${(d.rows||[]).map(r=>``).join('')}
Remove DateMeter #ManufacturerAccount #Service AddressInstall DateInstall ReadFinal ReadConsumptionTechnicianWork Order
${r.remove_date||'—'}${r.meter_number}${r.manufacturer||'—'}${r.account_number||'—'}${(r.service_address||'').slice(0,26)||'—'}${r.install_date||'—'}${r.read_at_install!=null?r.read_at_install+' kWh':'—'}${r.read_at_remove!=null?r.read_at_remove+' kWh':'—'}${r.consumption!=null?r.consumption.toLocaleString()+' kWh':'—'}${r.technician||'—'}${r.work_order||'—'}
`; }; const renderTechRpt = d => rptHdr('Field Technician Report',`${d.date_from} to ${d.date_to}`) + `
${statBox((d.summary||[]).length,'Active Techs','var(--accent2)')}${statBox((d.detail||[]).filter(r=>r.action_type==='INSTALLED').length,'Installs','var(--success)')}${statBox((d.detail||[]).filter(r=>r.action_type==='REMOVED').length,'Removals','var(--accent)')}${statBox((d.detail||[]).filter(r=>r.action_type==='DEFECTIVE').length,'Defectives','var(--danger)')}
` + `
${(d.summary||[]).map(r=>``).join('')}
TechnicianTotalInstallsRemovalsDefectivesUpdatesFirst ActivityLast Activity
${r.technician}${r.total_actions}${r.installs}${r.removals}${r.defectives}${r.updates}${r.first_activity||'—'}${r.last_activity||'—'}
` + (() => { const byTech={}; (d.detail||[]).forEach(r=>{ if(!byTech[r.technician])byTech[r.technician]=[]; byTech[r.technician].push(r); }); const tmap={INSTALLED:'log-install',REMOVED:'log-remove',ADDED:'log-new',UPDATED:'log-update',DEFECTIVE:'log-defect'}; const lmap={INSTALLED:'INSTALL',REMOVED:'REMOVE',ADDED:'NEW',UPDATED:'UPDATE',DEFECTIVE:'DEFECT'}; return Object.entries(byTech).map(([tech,rows])=>`
${tech} ${rows.length} actions
${rows.map(r=>``).join('')}
DateActionMeter #Account #Work OrderDetail
${(r.created_at||'').slice(0,10)}${lmap[r.action_type]||r.action_type}${r.meter_number||'—'}${r.account_number||'—'}${r.work_order||'—'}${r.action_detail||'—'}
`).join(''); })(); const renderActLogRpt = d => { const tc=d.type_counts||{}; const tmap={INSTALLED:'log-install',REMOVED:'log-remove',ADDED:'log-new',UPDATED:'log-update',DEFECTIVE:'log-defect'}; const lmap={INSTALLED:'INSTALL',REMOVED:'REMOVE',ADDED:'NEW',UPDATED:'UPDATE',DEFECTIVE:'DEFECT'}; return rptHdr('Activity Log Report',`${d.date_from} to ${d.date_to}`) + `
${statBox(d.total||0,'Total')}${statBox(tc.INSTALLED||0,'Installs','var(--success)')}${statBox(tc.REMOVED||0,'Removals','var(--accent)')}${statBox(tc.DEFECTIVE||0,'Defectives','var(--danger)')}${statBox(tc.ADDED||0,'Added','var(--purple)')}${statBox(tc.UPDATED||0,'Updates','var(--accent2)')}
` + `
${(d.rows||[]).map(r=>``).join('')}
TimestampActionMeter #Account #TechnicianWork OrderDetail
${(r.created_at||'').slice(0,16).replace('T',' ')}${lmap[r.action_type]||r.action_type}${r.meter_number||'—'}${r.account_number||'—'}${r.technician||'—'}${r.work_order||'—'}${r.action_detail||'—'}
`; }; function printReport() { document.getElementById('print-header').style.display='block'; window.print(); document.getElementById('print-header').style.display='none'; } function exportReportCSV() { if (!_lastReportData) { toast('Run a report first','error'); return; } const rows = _lastReportData.rows || _lastReportData.detail || _lastReportData.summary || []; if (!rows.length) { toast('No data to export','error'); return; } const keys=Object.keys(rows[0]); const csv=[keys.join(','),...rows.map(r=>keys.map(k=>JSON.stringify(r[k]??'')).join(','))].join('\n'); const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'})); a.download=`report_${_currentReport}_${new Date().toISOString().split('T')[0]}.csv`; a.click(); toast('Report exported'); } // ── Admin ───────────────────────────────────────────────────────────────────── async function loadAdmin() { await applyBranding(); const [{count:meters},{count:customers},{count:logs}] = await Promise.all([ db.from('meters').select('*',{count:'exact',head:true}), db.from('customers').select('*',{count:'exact',head:true}), db.from('activity_log').select('*',{count:'exact',head:true}), ]); document.getElementById('adm-cnt-meters').textContent = meters||0; document.getElementById('adm-cnt-customers').textContent = customers||0; document.getElementById('adm-cnt-log').textContent = logs||0; document.getElementById('adm-log-total').textContent = logs||0; const {data:oldest} = await db.from('activity_log').select('created_at').order('created_at',{ascending:true}).limit(1); const {data:newest} = await db.from('activity_log').select('created_at').order('created_at',{ascending:false}).limit(1); document.getElementById('adm-log-oldest').textContent = oldest?.[0]?.created_at?.slice(0,10) || '—'; document.getElementById('adm-log-newest').textContent = newest?.[0]?.created_at?.slice(0,10) || '—'; } async function clearLogBefore() { const date = document.getElementById('adm-clear-date').value; if (!date) { toast('Select a date first','error'); return; } if (!confirm(`Delete all log entries before ${date}?`)) return; await db.from('activity_log').delete().lt('created_at', date); toast(`Log entries before ${date} deleted`); loadAdmin(); } async function clearAllLog() { const {count} = await db.from('activity_log').select('*',{count:'exact',head:true}); if (!confirm(`Delete ALL ${count} log entries? This cannot be undone.`)) return; await db.from('activity_log').delete().neq('id',0); toast('All log entries cleared'); loadAdmin(); } // ── CSV Export ──────────────────────────────────────────────────────────────── async function exportCSV(table) { let data; if (table==='meters') { const {data:d}=await db.from('meters').select('*').order('id'); data=d; } else if (table==='customers') { const {data:d}=await db.from('customers').select('*').order('id'); data=d; } else if (table==='log') { const {data:d}=await db.from('activity_log').select('*').order('id'); data=d; } if (!data?.length) { toast('No data to export','error'); return; } const keys=Object.keys(data[0]); const csv=[keys.join(','),...data.map(r=>keys.map(k=>JSON.stringify(r[k]??'')).join(','))].join('\n'); const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'})); a.download=`${table}_${new Date().toISOString().split('T')[0]}.csv`; a.click(); toast('Exported'); }

Sample Page

This is an example page. It’s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:

Hi there! I’m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin’ caught in the rain.)

…or something like this:

The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.

As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!