{"id":23,"date":"2025-08-26T13:35:03","date_gmt":"2025-08-26T11:35:03","guid":{"rendered":"http:\/\/192.168.100.133\/?page_id=23"},"modified":"2026-02-13T12:58:53","modified_gmt":"2026-02-13T10:58:53","slug":"ae_user_track","status":"publish","type":"page","link":"http:\/\/www.nesh.co.za\/index.php\/ae_user_track\/","title":{"rendered":"User Growth"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" \/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" \/>\n<title>Atlas Earth User Tracker<\/title>\n<style>\n:root{\n  --blue:#3498db;\n  --green:#2ecc71;\n  --orange:#f39c12;\n  --red:#e74c3c;\n  --muted:#ecf0f1;\n  --card-width:720px;\n}\nbody {\n  font-family: Arial, sans-serif;\n  background: #f7f7f7;\n  margin: 0;\n  padding: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\nh1 { margin: 0 0 14px 0; font-size: 1.5rem; text-align:center; }\n.card {\n  width: 100%;\n  max-width: var(--card-width);\n  background: #fff;\n  border-radius: 10px;\n  padding: 14px;\n  box-shadow: 0 2px 8px rgba(0,0,0,0.08);\n  box-sizing: border-box;\n  margin-bottom: 14px;\n}\n.entry-grid {\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 8px;\n  align-items: center;\n}\n.input {\n  width: 100%;\n  padding: 10px 12px;\n  border-radius: 8px;\n  border: 1px solid #ccc;\n  font-size: 1rem;\n  box-sizing: border-box;\n}\n.controls {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n  justify-content: center;\n  margin-top: 8px;\n}\n.button {\n  padding: 10px 14px;\n  border: none;\n  border-radius: 8px;\n  cursor: pointer;\n  font-size: 0.95rem;\n  color: white;\n  min-width: 110px;\n  background: #6c7a89;\n}\n.button.button-add { background: var(--blue); }\n.button.button-report { background: var(--green); }\n.button.button-backup { background: var(--orange); }\n.button.button-restore { background: var(--red); }\n.button.button-monthly { background: #1abc9c; }\n.button.button-weekly { background: #9b59b6; }\n.button.button-yearly { background: #e67e22; }\n.button.button-compare { background: #34495e; }\n\n#message { margin-top: 8px; color: #2d7a2d; font-weight: 500; text-align:center; }\n#reportContainer, #chartContainer, #comparisonContainer { width: 100%; max-width: var(--card-width); margin-top: 8px; }\n\n.delete-week-btn {\n  background-color: #dc3545 !important;\n  color: white !important;\n  border: none;\n  padding: 6px 12px;\n  border-radius: 4px;\n  cursor: pointer;\n}\n\n.delete-week-btn:hover {\n  background-color: #a71d2a !important;\n}\n\n.user-card {\n  background: #fff;\n  border-radius: 8px;\n  padding: 12px;\n  margin-bottom: 12px;\n  box-shadow: 0 1px 6px rgba(0,0,0,0.05);\n}\n.user-header {\n  display:flex;\n  align-items:center;\n  justify-content:space-between;\n  gap:10px;\n  margin-bottom:8px;\n}\n.username-purple { color: purple; font-weight: 700; font-size:1rem; }\n.btn-small {\n  padding:6px 8px;\n  border-radius:6px;\n  border:none;\n  cursor:pointer;\n  font-size:0.85rem;\n  color:#fff;\n  min-width: 90px;\n  text-align:center;\n}\n.btn-delete { background: var(--red); }\n.btn-delete-month { background: var(--red); }\n.month-summary {\n  background: #d9eefc;\n  border-radius: 8px;\n  padding:10px;\n  display:flex;\n  justify-content:space-between;\n  align-items:center;\n  gap:10px;\n  margin-bottom:6px;\n}\n.month-info { font-weight:600; color:#0b4f8a; }\n.accordion-btn {\n  width:100%;\n  text-align:left;\n  background: var(--muted);\n  border:none;\n  padding:10px;\n  border-radius:8px;\n  cursor:pointer;\n  margin-bottom:6px;\n  font-size:0.95rem;\n}\n.accordion-btn.active { background:#cfe4fb; }\n.panel {\n  display:none;\n  background:#fff;\n  border-left:3px solid var(--blue);\n  padding:8px 10px;\n  border-radius:6px;\n  margin-bottom:8px;\n}\n\n.accordion-btn.monthly { color: #0b4f8a; }\n.accordion-btn.monthly.active { color: #072c4d; }\n.accordion-btn.weekly { color: #5e3370; }\n.accordion-btn.weekly.active { color: #361c44; }\n\n.week-row {\n  display:flex;\n  justify-content:space-between;\n  align-items:center;\n  padding:6px 0;\n  border-bottom:1px solid #eee;\n  cursor:pointer;\n}\n.week-row:last-child { border-bottom:none; }\n.week-details { font-size:0.92rem; color:#333; }\n\n@media (max-width: 640px) {\n  .card { padding:12px; }\n  .button { min-width: auto; padding:10px 12px; }\n  .entry-grid { gap:6px; }\n  #chartContainer canvas { height: 350px !important; }\n}\n\n#chartContainer {\n  width: 100%;\n  max-width: 720px;\n  margin-top: 8px;\n  min-height: 400px;\n}\n#chartContainer canvas {\n  width: 100% !important;\n  height: 100% !important;\n}\n\n.select-user, .checkbox-list { font-size: 0.9rem; }\n.select-user {\n  padding: 6px;\n  border-radius: 6px;\n  border: 1px solid #ccc;\n  background: #fff;\n  appearance: none;\n}\n.checkbox-list {\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n  max-height: 180px;\n  overflow-y: auto;\n  padding: 8px;\n  background: #fff;\n  border: 1px solid #ddd;\n  border-radius: 8px;\n}\n.checkbox-item { display: flex; align-items: center; gap: 8px; padding:4px 6px; border-radius:6px; }\n.checkbox-item input { transform: scale(1.05); margin-right:6px; }\n.checkbox-item label { cursor:pointer; }\n\n.summary {\n  margin-top: 10px;\n  padding: 10px 12px;\n  background: #f9f9f9;\n  border-left: 4px solid #3498db;\n  font-size: 14px;\n  line-height: 1.5;\n  color: #333;\n}\n#comparisonCanvas { width:100% !important; height:380px !important; }\n<\/style>\n<\/head>\n<body>\n\n<h1>Enter the total amount of parcels owned<\/h1>\n\n<div class=\"card\" role=\"region\" aria-label=\"Add new entry\">\n  <div class=\"entry-grid\">\n    <input id=\"username\" class=\"input\" placeholder=\"Username (will be uppercased)\" \/>\n    <input id=\"totalParcels\" class=\"input\" placeholder=\"Total Parcels\" type=\"number\" \/>\n    <input id=\"date\" class=\"input\" type=\"date\" \/>\n    <div class=\"controls\" style=\"margin-top:4px;\">\n      <button id=\"addEntry\" class=\"button button-add\" type=\"button\">Add Entry<\/button>\n    <\/div>\n    <div id=\"message\" aria-live=\"polite\"><\/div>\n  <\/div>\n<\/div>\n\n<div class=\"card\" style=\"text-align:center;\">\n  <div class=\"controls\" style=\"justify-content:center; flex-wrap:wrap;\">\n    <button id=\"createReport\" class=\"button button-report\">Report<\/button>\n    <button id=\"monthlyChartBtn\" class=\"button button-monthly\">Monthly Chart<\/button>\n    <button id=\"weeklyChartBtn\" class=\"button button-weekly\">Weekly Chart<\/button>\n    <button id=\"yearlyChartBtn\" class=\"button button-yearly\">Yearly Chart<\/button>\n    <button id=\"backupData\" class=\"button button-backup\">Backup<\/button>\n    <button id=\"restoreData\" class=\"button button-restore\">Restore<\/button>\n    <button id=\"userComparisonBtn\" class=\"button button-compare\">User Comparison<\/button>\n  <\/div>\n<\/div>\n\n<div id=\"reportContainer\" aria-live=\"polite\"><\/div>\n\n<div id=\"chartContainer\" class=\"card\" style=\"display:none;\">\n  <canvas id=\"chartCanvas\"><\/canvas>\n<\/div>\n\n<div id=\"comparisonContainer\" class=\"card\" style=\"display:none;\">\n  <div class=\"controls\" style=\"flex-wrap:wrap; justify-content:flex-start;\">\n    <div style=\"min-width:200px;\">\n      <label for=\"compMainUser\">Main User:<\/label>\n      <select id=\"compMainUser\" class=\"select-user\"><\/select>\n    <\/div>\n    <div style=\"flex:1;\">\n      <label>Compare Users:<\/label>\n      <div class=\"checkbox-list\" id=\"compUsersList\"><\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"controls\" style=\"justify-content:center; margin-top:8px;\">\n    <button id=\"compMonthlyChartBtn\" class=\"button button-monthly\">Monthly Chart<\/button>\n    <button id=\"compWeeklyChartBtn\" class=\"button button-weekly\">Weekly Chart<\/button>\n  <\/div>\n\n  <div style=\"margin-top:12px;\">\n    <canvas id=\"comparisonCanvas\"><\/canvas>\n    <div id=\"weeklySummary\" class=\"summary\" style=\"display:none;\"><\/div>\n    <div id=\"monthlySummary\" class=\"summary\" style=\"display:none;\"><\/div>\n  <\/div>\n<\/div>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js\"><\/script>\n<script>\ndocument.addEventListener('DOMContentLoaded', () => {\n  const usernameInput = document.getElementById('username');\n  const totalParcelsInput = document.getElementById('totalParcels');\n  const dateInput = document.getElementById('date');\n  const addEntryBtn = document.getElementById('addEntry');\n\n  const createReportBtn = document.getElementById('createReport');\n  const monthlyChartBtn = document.getElementById('monthlyChartBtn');\n  const weeklyChartBtn = document.getElementById('weeklyChartBtn');\n  const yearlyChartBtn = document.getElementById('yearlyChartBtn');\n  const backupBtn = document.getElementById('backupData');\n  const restoreBtn = document.getElementById('restoreData');\n\n  const reportContainer = document.getElementById('reportContainer');\n  const chartContainer = document.getElementById('chartContainer');\n  const chartCanvas = document.getElementById('chartCanvas');\n\n  const compareBtn = document.getElementById('userComparisonBtn');\n  const comparisonContainer = document.getElementById('comparisonContainer');\n  const compMainUser = document.getElementById('compMainUser');\n  const compUsersList = document.getElementById('compUsersList');\n  const compMonthlyBtn = document.getElementById('compMonthlyChartBtn');\n  const compWeeklyBtn = document.getElementById('compWeeklyChartBtn');\n  const compCanvas = document.getElementById('comparisonCanvas');\n\n  const messageDiv = document.getElementById('message');\n  const weeklySummaryDiv = document.getElementById('weeklySummary');\n  const monthlySummaryDiv = document.getElementById('monthlySummary');\n\n  const todayStr = new Date().toISOString().split('T')[0];\n  dateInput.value = todayStr;\n  let database = [];\n  let chartInstance = null;\n  let compChartInstance = null;\n  let lastComparisonType = 'monthly';\n\n  function pad(n){ return String(n).padStart(2,'0'); }\n  function parseDateStringToLocal(s){\n    if(!s) return null;\n    if(\/^\\d{4}-\\d{2}-\\d{2}$\/.test(s)){\n      const p = s.split('-').map(Number);\n      return new Date(p[0], p[1]-1, p[2]);\n    }\n    const d = new Date(s);\n    return isNaN(d)?null:d;\n  }\n  function formatLocalDate(d){\n    if(!d) return '';\n    if(typeof d === 'string' && \/^\\d{4}-\\d{2}-\\d{2}$\/.test(d)) return d;\n    return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;\n  }\n\n  function monthKeyFromDate(d){ return d.getFullYear() + '-' + pad(d.getMonth()+1); }\n  function yearKeyFromDate(d){ return d.getFullYear().toString(); }\n  function getWeekStart(d){\n    const date = (d instanceof Date) ? new Date(d.getTime()) : parseDateStringToLocal(d);\n    const day = date.getDay();\n    const diff = (day === 0 ? -6 : 1 - day);\n    date.setDate(date.getDate() + diff);\n    date.setHours(0,0,0,0);\n    return date;\n  }\n\n  function loadDatabase(){\n    try {\n      const raw = JSON.parse(localStorage.getItem('atlasDatabase') || '[]');\n      database = raw.map(r => ({\n        ...r,\n        date: parseDateStringToLocal(r.date)\n      })).filter(r => !isNaN(r.date));\n    } catch(err){\n      console.error(err);\n      database = [];\n    }\n  }\n\n  function saveDatabase(){\n    const serial = database.map(e => ({\n      ...e,\n      date: (e.date instanceof Date) ? formatLocalDate(e.date) : e.date\n    }));\n    localStorage.setItem('atlasDatabase', JSON.stringify(serial));\n  }\n\n  function showReport(){\n    chartContainer.style.display = 'none';\n    comparisonContainer.style.display = 'none';\n    reportContainer.style.display = 'block';\n  }\n  function showChart(){\n    reportContainer.style.display = 'none';\n    comparisonContainer.style.display = 'none';\n    chartContainer.style.display = 'block';\n    weeklySummaryDiv.style.display='none';\n    monthlySummaryDiv.style.display='none';\n  }\n  function showComparison(){\n    reportContainer.style.display = 'none';\n    chartContainer.style.display = 'none';\n    comparisonContainer.style.display = 'block';\n    weeklySummaryDiv.style.display='none';\n    monthlySummaryDiv.style.display='none';\n    populateComparisonSelectors();\n  }\n\n  addEntryBtn.addEventListener('click', () => {\n    loadDatabase();\n    const usernameRaw = usernameInput.value.trim();\n    const total = parseInt(totalParcelsInput.value, 10);\n    const dateValue = dateInput.value;\n    if (!usernameRaw || isNaN(total) || !dateValue) {\n      alert('Please enter username, total parcels and a valid date.');\n      return;\n    }\n\n    const username = usernameRaw.toUpperCase();\n    const dateObj = parseDateStringToLocal(dateValue);\n    if(!dateObj){ alert('Invalid date'); return; }\n\n    database.push({ username, total, date: dateObj });\n    saveDatabase();\n    usernameInput.value = username;\n    totalParcelsInput.value = '';\n    dateInput.value = todayStr;\n\n    messageDiv.textContent = `Entry added for ${username} on ${dateValue} (${total})`;\n    setTimeout(() => messageDiv.textContent = '', 3000);\n    createReportBtn.click();\n  });\n\n  function deleteUserData(username){\n    if(!confirm(`Delete ALL data for ${username}?`)) return;\n    loadDatabase();\n    database = database.filter(e => e.username.toUpperCase() !== username.toUpperCase());\n    saveDatabase();\n    createReportBtn.click();\n  }\n  function deleteMonthData(username, monthKey){\n    if(!confirm(`Delete all data for ${username} in ${monthKey}?`)) return;\n    loadDatabase();\n    database = database.filter(e => !(e.username.toUpperCase() === username.toUpperCase() && monthKeyFromDate(e.date) === monthKey));\n    saveDatabase();\n    createReportBtn.click();\n  }\n  function deleteWeekData(username, weekEndStr){\n    if(!confirm(`Delete all data for ${username} for week ending ${weekEndStr}?`)) return;\n    loadDatabase();\n    database = database.filter(e => {\n      if (e.username.toUpperCase() !== username.toUpperCase()) return true;\n      const ws = getWeekStart(e.date);\n      const we = new Date(ws); we.setDate(we.getDate() + 6);\n      return formatLocalDate(we) !== weekEndStr;\n    });\n    saveDatabase();\n    createReportBtn.click();\n  }\n\n  createReportBtn.addEventListener('click', () => {\n    showReport();\n    loadDatabase();\n    reportContainer.innerHTML = '';\n    if (!database.length) { reportContainer.textContent = 'No data available.'; return; }\n\n    const grouped = {};\n    database.forEach(e => {\n      const key = e.username.toUpperCase();\n      if (!grouped[key]) grouped[key] = [];\n      grouped[key].push({ ...e, username: key });\n    });\n\n    const usernames = Object.keys(grouped).sort((a,b) => a.localeCompare(b));\n\n    usernames.forEach(username => {\n      const entries = grouped[username].sort((a,b) => a.date - b.date);\n      const userCard = document.createElement('div'); userCard.className='user-card';\n\n      const hdr = document.createElement('div'); hdr.className='user-header';\n      const nameDiv = document.createElement('div'); nameDiv.className='username-purple'; nameDiv.textContent=username;\n      hdr.appendChild(nameDiv);\n      const userActions = document.createElement('div');\n      const delUserBtn = document.createElement('button'); delUserBtn.className='btn-small btn-delete'; delUserBtn.textContent='Delete User';\n      delUserBtn.addEventListener('click',()=>deleteUserData(username));\n      userActions.appendChild(delUserBtn);\n      hdr.appendChild(userActions);\n      userCard.appendChild(hdr);\n\n      const monthsMap = {};\n      entries.forEach(e => {\n        const mk = monthKeyFromDate(e.date);\n        if (!monthsMap[mk]) monthsMap[mk] = [];\n        monthsMap[mk].push(e);\n      });\n\n      \/\/ FIXED: Explicit newest-first sort (descending year\/month)\n      const monthKeys = Object.keys(monthsMap).sort((a, b) => {\n        const [yearA, monthA] = a.split('-').map(Number);\n        const [yearB, monthB] = b.split('-').map(Number);\n        if (yearA !== yearB) return yearB - yearA; \/\/ higher year first\n        return monthB - monthA;                    \/\/ higher month first\n      });\n\n      const renderMonth = (monthKey, targetContainer) => {\n        const monthEntries = monthsMap[monthKey].sort((a,b)=>a.date-b.date);\n        const monthStartDate = new Date(monthEntries[0].date.getFullYear(), monthEntries[0].date.getMonth(), 1);\n\n        const prior = entries.filter(en => en.date < monthStartDate);\n        const baseline = prior.length ? prior[prior.length-1].total : monthEntries[0].total;\n        const monthTotal = monthEntries[monthEntries.length-1].total;\n        const monthGrowth = monthTotal - baseline;\n\n        const monthBar = document.createElement('div'); monthBar.className='month-summary';\n        const info = document.createElement('div'); info.className='month-info';\n        info.innerHTML = `<div><b>Month:<\/b> ${monthKey}<\/div><div>Total Parcels: ${monthTotal}<\/div><div>Monthly Growth: ${monthGrowth}<\/div>`;\n        monthBar.appendChild(info);\n        const monthActions = document.createElement('div');\n        const delMonthBtn = document.createElement('button'); delMonthBtn.className='btn-small btn-delete-month'; delMonthBtn.textContent='Delete Month';\n        delMonthBtn.addEventListener('click',(ev)=>{ ev.stopPropagation(); deleteMonthData(username, monthKey); });\n        monthActions.appendChild(delMonthBtn);\n        monthBar.appendChild(monthActions);\n        targetContainer.appendChild(monthBar);\n\n        const accordionBtn = document.createElement('button');\n        accordionBtn.className='accordion-btn weekly';\n        accordionBtn.textContent='Show Weekly Data';\n        targetContainer.appendChild(accordionBtn);\n\n        const panel = document.createElement('div'); panel.className='panel';\n\n        const weeksMap = {};\n        monthEntries.forEach(e => {\n          const ws = getWeekStart(e.date);\n          const we = new Date(ws); we.setDate(we.getDate()+6);\n          const key = formatLocalDate(we);\n          if (!weeksMap[key]) weeksMap[key] = [];\n          weeksMap[key].push(e);\n        });\n        const weekKeys = Object.keys(weeksMap).sort((a,b)=>new Date(a)-new Date(b));\n\n        const weekGrowths = weekKeys.map(weekEndStr => {\n          const weekEntries = weeksMap[weekEndStr].sort((a,b)=>a.date-b.date);\n          const weekStartDate = getWeekStart(weekEntries[0].date);\n          const priorForWeek = entries.filter(en=>en.date<weekStartDate);\n          const baselineWeek = priorForWeek.length ? priorForWeek[priorForWeek.length-1].total : weekEntries[0].total;\n          const weekTotal = weekEntries[weekEntries.length-1].total;\n          const growth = weekTotal - baselineWeek;\n          return { weekEndStr, growth, weekEntries };\n        });\n        const avgGrowth = weekGrowths.reduce((sum,w)=>sum+w.growth,0) \/ (weekGrowths.length||1);\n\n        weekGrowths.forEach(w => {\n          const weekRow = document.createElement('div'); weekRow.className='week-row';\n          const left = document.createElement('div'); left.className='week-details';\n          const today = new Date(new Date().toISOString().split('T')[0]+'T00:00:00');\n          const isComplete = parseDateStringToLocal(w.weekEndStr) < today;\n          left.innerHTML = isComplete\n            ? `${w.weekEndStr} \u2192 Total Parcels = ${w.weekEntries[w.weekEntries.length-1].total}, Growth = ${w.growth}`\n            : `${w.weekEndStr} \u2192 Total Parcels = ${w.weekEntries[w.weekEntries.length-1].total}, Growth (in progress) = ${w.growth} (Avg ${avgGrowth.toFixed(1)})`;\n          weekRow.appendChild(left);\n\n          const detailsDiv = document.createElement('div'); detailsDiv.className='week-details'; detailsDiv.style.display='none';\n          w.weekEntries.forEach(e => {\n            const row = document.createElement('div');\n            row.textContent = `${formatLocalDate(e.date)} \u2192 ${e.total}`;\n            detailsDiv.appendChild(row);\n          });\n\n          const avgDiv = document.createElement('div');\n          avgDiv.className = 'week-details';\n          avgDiv.style.fontStyle = 'italic';\n          avgDiv.style.color = '#2980b9';\n          avgDiv.textContent = `Average weekly growth for this month: ${avgGrowth.toFixed(1)}`;\n          avgDiv.style.display='none';\n\n          const delWeekBtn = document.createElement('button');\n          delWeekBtn.className = 'btn-small delete-week-btn del-week';\n          delWeekBtn.textContent='Delete Week';\n          delWeekBtn.addEventListener('click', ev=>{ ev.stopPropagation(); deleteWeekData(username, w.weekEndStr); });\n\n          weekRow.addEventListener('click', () => {\n            const isOpen = detailsDiv.style.display==='block';\n            detailsDiv.style.display = isOpen ? 'none' : 'block';\n            avgDiv.style.display = isOpen ? 'none' : 'block';\n          });\n\n          panel.appendChild(weekRow);\n          panel.appendChild(detailsDiv);\n          panel.appendChild(avgDiv);\n          panel.appendChild(delWeekBtn);\n        });\n\n        accordionBtn.addEventListener('click', function(){\n          const open = panel.style.display==='block';\n          panel.style.display = open ? 'none' : 'block';\n          this.classList.toggle('active', !open);\n          this.textContent = open ? 'Show Weekly Data' : 'Hide Weekly Data';\n        });\n\n        targetContainer.appendChild(panel);\n      };\n\n      if (monthKeys.length) {\n        \/\/ Now monthKeys[0] is guaranteed to be the newest month\n        renderMonth(monthKeys[0], userCard);\n\n        if (monthKeys.length > 1) {\n          const olderBtn = document.createElement('button');\n          olderBtn.className = 'accordion-btn monthly';\n          olderBtn.textContent = 'Show Older Months';\n\n          const olderPanel = document.createElement('div');\n          olderPanel.className = 'panel';\n\n          \/\/ Render remaining (older) months\n          monthKeys.slice(1).forEach(mk => renderMonth(mk, olderPanel));\n\n          olderBtn.addEventListener('click', function(){\n            const open = olderPanel.style.display==='block';\n            olderPanel.style.display = open ? 'none' : 'block';\n            this.classList.toggle('active', !open);\n            this.textContent = open ? 'Show Older Months' : 'Hide Older Months';\n          });\n\n          userCard.appendChild(olderBtn);\n          userCard.appendChild(olderPanel);\n        }\n      }\n\n      reportContainer.appendChild(userCard);\n    });\n  });\n\n  backupBtn.addEventListener('click', () => {\n    loadDatabase();\n    const blob=new Blob([JSON.stringify(database.map(e=>({...e,date: formatLocalDate(e.date)})),null,2)],{type:'application\/json'});\n    const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='atlasDatabase_backup.json'; a.click(); URL.revokeObjectURL(a.href);\n    messageDiv.textContent='Backup downloaded.'; setTimeout(()=>messageDiv.textContent='',2500);\n  });\n  restoreBtn.addEventListener('click',()=>{\n    const input=document.createElement('input'); input.type='file'; input.accept='.json,application\/json';\n    input.onchange=(e)=>{ \n      const file=e.target.files[0]; const reader=new FileReader();\n      reader.onload=ev=>{\n        try{\n          const imported=JSON.parse(ev.target.result)||[];\n          database=imported.map(x=>({...x,date: parseDateStringToLocal(x.date) })).filter(x=>!isNaN(x.date));\n          saveDatabase(); messageDiv.textContent='Data restored.'; setTimeout(()=>messageDiv.textContent='',2500); createReportBtn.click();\n        }catch(err){ alert('Invalid file.'); }\n      }; reader.readAsText(file);\n    }; input.click();\n  });\n\n  function generateMonthlyChart(){\n    showChart(); loadDatabase();\n    if(!database.length){ chartContainer.innerHTML='No data'; return; }\n    const ctx = chartCanvas.getContext('2d');\n\n    const grouped={};\n    database.forEach(e=>{ const key=e.username.toUpperCase(); if(!grouped[key]) grouped[key]=[]; grouped[key].push(e); });\n\n    const monthGrowths={};\n    Object.keys(grouped).forEach(u=>{\n      const entries=grouped[u].sort((a,b)=>a.date-b.date);\n      const monthsMap={};\n      entries.forEach(e=>{ const mk=monthKeyFromDate(e.date); if(!monthsMap[mk]) monthsMap[mk]=[]; monthsMap[mk].push(e); });\n      monthGrowths[u]={};\n      Object.keys(monthsMap).forEach(mk=>{\n        const monthEntries=monthsMap[mk].sort((a,b)=>a.date-b.date);\n        const monthStartDate=new Date(monthEntries[0].date.getFullYear(),monthEntries[0].date.getMonth(),1);\n        const prior=entries.filter(en=>en.date<monthStartDate);\n        const baseline=prior.length?prior[prior.length-1].total:monthEntries[0].total;\n        const monthTotal=monthEntries[monthEntries.length-1].total;\n        monthGrowths[u][mk]=monthTotal-baseline;\n      });\n    });\n\n    const allMonths=[...new Set([].concat(...Object.values(monthGrowths).map(o=>Object.keys(o))))].sort((a,b)=>new Date(a+'-01')-new Date(b+'-01'));\n    const last4 = allMonths.slice(-4);\n\n    const datasets=Object.keys(monthGrowths).map((u,i)=>({\n      label:u,\n      data:last4.map(mk=>monthGrowths[u][mk]||0),\n      backgroundColor:`hsl(${(i*60) % 360},70%,50%)`\n    }));\n\n    if(chartInstance) chartInstance.destroy();\n    chartInstance=new Chart(ctx,{\n      type:'bar',\n      data:{labels:last4,datasets:datasets},\n      options:{responsive:true,maintainAspectRatio:false,plugins:{title:{display:true,text:'Monthly Growth Comparison (Latest 4 Months)'}},scales:{y:{beginAtZero:true}}}\n    });\n  }\n\n  function generateWeeklyChart(){\n    showChart(); loadDatabase();\n    if(!database.length){ chartContainer.innerHTML='No data'; return; }\n    const ctx = chartCanvas.getContext('2d');\n\n    const grouped={};\n    database.forEach(e=>{ const key=e.username.toUpperCase(); if(!grouped[key]) grouped[key]=[]; grouped[key].push(e); });\n    const weekGrowths={};\n    Object.keys(grouped).forEach(u=>{\n      const entries=grouped[u].sort((a,b)=>a.date-b.date);\n      const weeksMap={};\n      entries.forEach(e=>{ const ws=getWeekStart(e.date); const we=new Date(ws); we.setDate(we.getDate()+6); const key=formatLocalDate(we); if(!weeksMap[key]) weeksMap[key]=[]; weeksMap[key].push(e); });\n      const weekKeys=Object.keys(weeksMap).sort((a,b)=>new Date(a)-new Date(b));\n      const last4 = weekKeys.slice(-4);\n      weekGrowths[u]={};\n      last4.forEach(we=>{\n        const weekEntries=weeksMap[we].sort((a,b)=>a.date-b.date);\n        const weekStart=getWeekStart(weekEntries[0].date);\n        const prior=entries.filter(en=>en.date<weekStart);\n        const baseline=prior.length?prior[prior.length-1].total:weekEntries[0].total;\n        const weekTotal=weekEntries[weekEntries.length-1].total;\n        weekGrowths[u][we]=weekTotal-baseline;\n      });\n    });\n\n    const allWeeks=[...new Set([].concat(...Object.values(weekGrowths).map(o=>Object.keys(o))))].sort((a,b)=>new Date(a)-new Date(b)).slice(-4);\n    const datasets=Object.keys(weekGrowths).map((u,i)=>({label:u,data:allWeeks.map(w=>weekGrowths[u][w]||0),backgroundColor:`hsl(${(i*60)%360},70%,50%)`}));\n\n    if(chartInstance) chartInstance.destroy();\n    chartInstance=new Chart(ctx,{\n      type:'bar',\n      data:{labels:allWeeks,datasets:datasets},\n      options:{responsive:true,maintainAspectRatio:false,plugins:{title:{display:true,text:'Weekly Growth Comparison (Latest 4 Weeks)'}},scales:{y:{beginAtZero:true}}}\n    });\n  }\n\n  function generateYearlyChart(){\n    showChart(); loadDatabase();\n    if(!database.length){ chartContainer.innerHTML='No data'; return; }\n    const ctx = chartCanvas.getContext('2d');\n\n    const grouped={};\n    database.forEach(e=>{ const key=e.username.toUpperCase(); if(!grouped[key]) grouped[key]=[]; grouped[key].push(e); });\n\n    const yearGrowths={};\n    Object.keys(grouped).forEach(u=>{\n      const entries=grouped[u].sort((a,b)=>a.date-b.date);\n      const yearsMap={};\n      entries.forEach(e=>{ const y=yearKeyFromDate(e.date); if(!yearsMap[y]) yearsMap[y]=[]; yearsMap[y].push(e); });\n      yearGrowths[u]={};\n      Object.keys(yearsMap).forEach(y=>{\n        const yEntries = yearsMap[y].sort((a,b)=>a.date-b.date);\n        const yStart = new Date(parseInt(y), 0, 1);\n        const prior = entries.filter(en=>en.date < yStart);\n        const baseline = prior.length ? prior[prior.length-1].total : yEntries[0].total;\n        const endTotal = yEntries[yEntries.length-1].total;\n        yearGrowths[u][y] = endTotal - baseline;\n      });\n    });\n\n    const allYears = [...new Set([].concat(...Object.values(yearGrowths).map(o=>Object.keys(o))))]\n      .sort((a,b)=>parseInt(a)-parseInt(b));\n    const displayed = allYears.slice(-3);\n\n    const datasets = Object.keys(yearGrowths).map((u,i)=>({\n      label: u,\n      data: displayed.map(y => yearGrowths[u][y] || 0),\n      backgroundColor: `hsl(${(i*90)%360}, 75%, 50%)`\n    }));\n\n    if(chartInstance) chartInstance.destroy();\n    chartInstance = new Chart(ctx,{\n      type:'bar',\n      data:{ labels:displayed, datasets },\n      options:{\n        responsive:true,\n        maintainAspectRatio:false,\n        plugins:{ title:{display:true, text:'Yearly Parcel Growth (Last 3 Years)'}},\n        scales:{ y:{beginAtZero:true} }\n      }\n    });\n  }\n\n  monthlyChartBtn.addEventListener('click', generateMonthlyChart);\n  weeklyChartBtn.addEventListener('click', generateWeeklyChart);\n  yearlyChartBtn.addEventListener('click', generateYearlyChart);\n\n  compareBtn.addEventListener('click', showComparison);\n\n  function populateComparisonSelectors(){\n    loadDatabase();\n    const usernames = [...new Set(database.map(e=> (e.username||'').toUpperCase()))].sort();\n    compMainUser.innerHTML = usernames.map(u=>`<option value=\"${u}\">${u}<\/option>`).join('');\n    compUsersList.innerHTML = '';\n    usernames.forEach((u, idx) =>{\n      const wrap = document.createElement('div');\n      wrap.className='checkbox-item';\n      const cb = document.createElement('input'); cb.type='checkbox'; cb.value=u; cb.id = 'cb_'+u;\n      cb.checked = (u !== usernames[0]);\n      const lbl = document.createElement('label'); lbl.htmlFor='cb_'+u; lbl.textContent=u;\n      wrap.appendChild(cb); wrap.appendChild(lbl);\n      compUsersList.appendChild(wrap);\n    });\n\n    if(usernames.length) compMainUser.value = usernames[0];\n\n    if(usernames.length) {\n      lastComparisonType = 'monthly';\n      generateMonthlyComparison();\n    }\n  }\n\n  function generateMonthlyComparison(){\n    loadDatabase();\n    if (compChartInstance) { compChartInstance.destroy(); compChartInstance = null; }\n    const ctx = compCanvas.getContext('2d');\n\n    const mainUser = compMainUser.value;\n    const compareUsers = Array.from(compUsersList.querySelectorAll('input:checked')).map(cb => cb.value);\n\n    if (!mainUser) { alert('Select a main user'); return; }\n\n    function getMonthKey(d){\n      const date = (d instanceof Date) ? d : parseDateStringToLocal(d);\n      return date.getFullYear() + \"-\" + String(date.getMonth() + 1).padStart(2,'0');\n    }\n\n    function userMonthGrowth(username) {\n      const entries = database\n        .filter(e => e.username.toUpperCase() === username.toUpperCase())\n        .sort((a, b) => a.date - b.date);\n\n      if (entries.length === 0) return {};\n\n      const monthsMap = {};\n      entries.forEach(e => {\n        const key = getMonthKey(e.date);\n        if (!monthsMap[key]) monthsMap[key] = [];\n        monthsMap[key].push(e);\n      });\n\n      const growthPerMonth = {};\n      const allMonthKeys = Object.keys(monthsMap).sort();\n\n      allMonthKeys.forEach((monthKey, idx) => {\n        const monthEntries = monthsMap[monthKey].sort((a, b) => a.date - b.date);\n        const endTotal = monthEntries[monthEntries.length - 1].total;\n\n        let baseline = 0;\n        if (idx > 0) {\n          const prevMonthKey = allMonthKeys[idx - 1];\n          const prevEntries = monthsMap[prevMonthKey];\n          baseline = prevEntries[prevEntries.length - 1].total;\n        } else {\n          const monthStart = new Date(monthKey + \"-01\");\n          const priorEntries = entries.filter(e => e.date < monthStart);\n          if (priorEntries.length > 0) {\n            priorEntries.sort((a, b) => b.date - a.date);\n            baseline = priorEntries[0].total;\n          }\n        }\n\n        growthPerMonth[monthKey] = endTotal - baseline;\n      });\n\n      const last4Keys = allMonthKeys.slice(-4);\n      const result = {};\n      last4Keys.forEach(k => {\n        result[k] = growthPerMonth[k] || 0;\n      });\n      return result;\n    }\n\n    const mainGrowth = userMonthGrowth(mainUser);\n    const compareGrowth = compareUsers\n      .map(userMonthGrowth)\n      .reduce((acc, g) => {\n        Object.keys(g).forEach(mk => { acc[mk] = (acc[mk] || 0) + g[mk]; });\n        return acc;\n      }, {});\n\n    const allMonths = [...new Set([...Object.keys(mainGrowth), ...Object.keys(compareGrowth)])]\n      .sort()\n      .slice(-4);\n\n    const mainValues = allMonths.map(mk => mainGrowth[mk] || 0);\n    const compareValues = allMonths.map(mk => compareGrowth[mk] || 0);\n    const mainAvg = mainValues.reduce((a,b)=>a+b,0) \/ (mainValues.length||1);\n    const compareAvg = compareValues.reduce((a,b)=>a+b,0) \/ (compareValues.length||1);\n\n    compChartInstance = new Chart(ctx, {\n      type: 'bar',\n      data: {\n        labels: allMonths,\n        datasets: [\n          { label: mainUser, data: mainValues, backgroundColor: '#3498db' },\n          { label: 'Comparison Users', data: compareValues, backgroundColor: '#9b59b6' },\n          { label: mainUser + \" Avg\", data: allMonths.map(()=>mainAvg), type:'line', borderColor:'#2980b9', borderWidth:2, fill:false, pointRadius:0, borderDash:[5,5] },\n          { label: \"Comparison Avg\", data: allMonths.map(()=>compareAvg), type:'line', borderColor:'#8e44ad', borderWidth:2, fill:false, pointRadius:0, borderDash:[5,5] }\n        ]\n      },\n      options: { \n        responsive: true, \n        maintainAspectRatio: false, \n        plugins:{ title:{ display:true, text:'Monthly Growth (Latest 4 Months) + Averages' }}, \n        scales:{ y:{ beginAtZero:true } } \n      }\n    });\n\n    const monthlyData = allMonths.map(mk => ({\n      month: mk,\n      mainUserGrowth: mainGrowth[mk] || 0,\n      compareUsersGrowth: compareGrowth[mk] || 0\n    }));\n    let text = \"<h4>Monthly Summary<\/h4><ul>\";\n    monthlyData.forEach(it => {\n      let monthlyColor = it.mainUserGrowth > it.compareUsersGrowth ? \"green\" : \"red\";\n      text += `<li style=\"color:${monthlyColor}\">${it.month}: ${mainUser} = ${it.mainUserGrowth}, comparison = ${it.compareUsersGrowth}<\/li>`;\n    });\n    text += `<li><b>Averages:<\/b> <span style=\"color:#2980b9\">${mainUser} = ${mainAvg.toFixed(1)}<\/span>, <span style=\"color:#8e44ad\">Comparison = ${compareAvg.toFixed(1)}<\/span><\/li>`;\n    text += \"<\/ul>\";\n\n    monthlySummaryDiv.innerHTML = text;\n    monthlySummaryDiv.style.display = 'block';\n    weeklySummaryDiv.style.display = 'none';\n  }\n\n  function generateWeeklyComparison(){\n    loadDatabase();\n    if (compChartInstance) { compChartInstance.destroy(); compChartInstance = null; }\n    const ctx = compCanvas.getContext('2d');\n\n    const mainUser = compMainUser.value;\n    const compareUsers = Array.from(compUsersList.querySelectorAll('input:checked')).map(cb => cb.value);\n\n    if (!mainUser) { alert('Select a main user'); return; }\n\n    function getWeekStartLocal(d){\n      const date = (d instanceof Date) ? new Date(d.getTime()) : parseDateStringToLocal(d);\n      const day = date.getDay();\n      const diff = (day === 0 ? -6 : 1 - day);\n      date.setDate(date.getDate() + diff);\n      date.setHours(0,0,0,0);\n      return date;\n    }\n\n    function userWeekGrowth(username) {\n      const entries = database\n        .filter(e => e.username.toUpperCase() === username.toUpperCase())\n        .sort((a, b) => a.date - b.date);\n\n      if (entries.length === 0) return {};\n\n      const weeksMap = {};\n      entries.forEach(e => {\n        const ws = getWeekStartLocal(e.date);\n        const we = new Date(ws); we.setDate(we.getDate() + 6);\n        const key = formatLocalDate(we);\n        if (!weeksMap[key]) weeksMap[key] = [];\n        weeksMap[key].push(e);\n      });\n\n      const growthPerWeek = {};\n      const allWeekKeys = Object.keys(weeksMap).sort();\n\n      allWeekKeys.forEach((weekKey, idx) => {\n        const weekEntries = weeksMap[weekKey].sort((a, b) => a.date - b.date);\n        const endTotal = weekEntries[weekEntries.length - 1].total;\n\n        let baseline = 0;\n        if (idx > 0) {\n          const prevWeekKey = allWeekKeys[idx - 1];\n          const prevEntries = weeksMap[prevWeekKey];\n          baseline = prevEntries[prevEntries.length - 1].total;\n        } else {\n          const weekStart = getWeekStartLocal(weekEntries[0].date);\n          const prior = entries.filter(e => e.date < weekStart);\n          if (prior.length > 0) {\n            prior.sort((a, b) => b.date - a.date);\n            baseline = prior[0].total;\n          }\n        }\n\n        growthPerWeek[weekKey] = endTotal - baseline;\n      });\n\n      const last4Keys = allWeekKeys.slice(-4);\n      const result = {};\n      last4Keys.forEach(k => {\n        result[k] = growthPerWeek[k] || 0;\n      });\n      return result;\n    }\n\n    const mainGrowth = userWeekGrowth(mainUser);\n    const compareGrowth = compareUsers\n      .map(userWeekGrowth)\n      .reduce((acc, g) => {\n        Object.keys(g).forEach(wk => { acc[wk] = (acc[wk] || 0) + g[wk]; });\n        return acc;\n      }, {});\n\n    const allWeeks = [...new Set([...Object.keys(mainGrowth), ...Object.keys(compareGrowth)])]\n      .sort()\n      .slice(-4);\n\n    const mainValues = allWeeks.map(wk => mainGrowth[wk] || 0);\n    const compareValues = allWeeks.map(wk => compareGrowth[wk] || 0);\n    const mainAvg = mainValues.reduce((a,b)=>a+b,0) \/ (mainValues.length||1);\n    const compareAvg = compareValues.reduce((a,b)=>a+b,0) \/ (compareValues.length||1);\n\n    compChartInstance = new Chart(ctx, {\n      type: 'bar',\n      data: {\n        labels: allWeeks,\n        datasets: [\n          { label: mainUser, data: mainValues, backgroundColor: '#3498db' },\n          { label: 'Comparison Users', data: compareValues, backgroundColor: '#9b59b6' },\n          { label: mainUser + \" Avg\", data: allWeeks.map(()=>mainAvg), type:'line', borderColor:'#2980b9', borderWidth:2, fill:false, pointRadius:0, borderDash:[5,5] },\n          { label: \"Comparison Avg\", data: allWeeks.map(()=>compareAvg), type:'line', borderColor:'#8e44ad', borderWidth:2, fill:false, pointRadius:0, borderDash:[5,5] }\n        ]\n      },\n      options: { \n        responsive: true, \n        maintainAspectRatio: false, \n        plugins:{ title:{ display:true, text:'Weekly Growth (Latest 4 Weeks) + Averages' }}, \n        scales:{ y:{ beginAtZero:true } } \n      }\n    });\n\n    const weeklyData = allWeeks.map(wk => ({\n      week: wk,\n      mainUserGrowth: mainGrowth[wk] || 0,\n      compareUsersGrowth: compareGrowth[wk] || 0\n    }));\n    let text = \"<h4>Weekly Summary<\/h4><ul>\";\n    weeklyData.forEach(it => {\n      let weeklyColor = it.mainUserGrowth > it.compareUsersGrowth ? \"green\" : \"red\";\n      text += `<li style=\"color:${weeklyColor}\">${it.week}: ${mainUser} = ${it.mainUserGrowth}, comparison = ${it.compareUsersGrowth}<\/li>`;\n    });\n    text += `<li><b>Averages:<\/b> <span style=\"color:#2980b9\">${mainUser} = ${mainAvg.toFixed(1)}<\/span>, <span style=\"color:#8e44ad\">Comparison = ${compareAvg.toFixed(1)}<\/span><\/li>`;\n    text += \"<\/ul>\";\n\n    weeklySummaryDiv.innerHTML = text;\n    weeklySummaryDiv.style.display = 'block';\n    monthlySummaryDiv.style.display = 'none';\n  }\n\n  compMonthlyBtn.addEventListener('click', () => {\n    lastComparisonType = 'monthly';\n    generateMonthlyComparison();\n  });\n\n  compWeeklyBtn.addEventListener('click', () => {\n    lastComparisonType = 'weekly';\n    generateWeeklyComparison();\n  });\n\n  compMainUser.addEventListener('change', () => {\n    if (lastComparisonType === 'monthly') generateMonthlyComparison();\n    else generateWeeklyComparison();\n  });\n\n  compUsersList.addEventListener('change', () => {\n    if (lastComparisonType === 'monthly') generateMonthlyComparison();\n    else generateWeeklyComparison();\n  });\n\n  loadDatabase();\n  createReportBtn.click();\n});\n<\/script>\n<\/body>\n<\/html>\n\n\n\n<p class=\"has-text-align-center\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Atlas Earth User Tracker Enter the total amount of parcels owned Add Entry Report Monthly Chart Weekly Chart Yearly Chart Backup Restore User Comparison Main User: Compare Users: Monthly Chart Weekly Chart<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-23","page","type-page","status-publish"],"_links":{"self":[{"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/pages\/23","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/comments?post=23"}],"version-history":[{"count":17,"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/pages\/23\/revisions"}],"predecessor-version":[{"id":96,"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/pages\/23\/revisions\/96"}],"wp:attachment":[{"href":"http:\/\/www.nesh.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=23"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}