OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-booking / web / js / dashboard.js

1302 lines · javascript ref:
/**
 * Dashboard client-side logic.
 * Each view has its own init function.
 */

/* global HyperBookingIdentity, api, showToast, formatTime, formatDate, formatPrice */

const content = document.getElementById('content')

async function initDashboard (view, params) {
  // Check auth first
  try {
    const me = await api('GET', '/api/auth/me')
    const nameEl = document.getElementById('user-name')
    if (nameEl) nameEl.textContent = me.username || me.userId || ''

    // Show admin link if admin
    if (me.role === 'admin') {
      const adminLink = document.getElementById('admin-link')
      if (adminLink) adminLink.style.display = ''
    }

    if (!me.authenticated && view !== 'profile') {
      content.innerHTML = '<p class="empty">Please <a href="javascript:void(0)" onclick="signIn()">sign in</a> to access the dashboard.</p>'
      return
    }
  } catch (err) {
    content.innerHTML = '<p class="empty">Please sign in to access the dashboard.</p>'
    return
  }

  // Auto-claim unclaimed provider on first dashboard visit
  if (view === 'home' || view === 'clients' || view === 'packages' || view === 'availability' || view === 'analytics' || view === 'settings') {
    try {
      const provData = await api('GET', '/api/dashboard/provider')
      if (!provData.provider) {
        renderCreateProvider()
        return
      }
    } catch (err) {
      // Non-blocking — continue anyway
    }
  }

  switch (view) {
    case 'home': return renderCalendar()
    case 'clients': return renderClients()
    case 'client-detail': return renderClientDetail(params.clientId)
    case 'packages': return renderPackages()
    case 'package-edit': return renderPackageEdit(params.id)
    case 'availability': return renderAvailability()
    case 'analytics': return renderAnalytics()
    case 'settings': return renderSettings()
    case 'profile': return renderProfile()
    case 'portal': return renderPortal()
    case 'admin': return renderAdmin()
    case 'admin-providers': return renderAdminProviders()
    case 'admin-users': return renderAdminUsers()
    default: content.innerHTML = '<p class="empty">Unknown view</p>'
  }
}

async function signIn () {
  if (typeof HyperBookingIdentity !== 'undefined') {
    await HyperBookingIdentity.generate()
    showToast('Identity created', 'success')
    location.reload()
  }
}

// ---- Calendar (Home) ----

async function renderCalendar () {
  try {
    const [today, week] = await Promise.all([
      api('GET', '/api/dashboard/today'),
      api('GET', '/api/dashboard/week')
    ])

    const todayFmt = new Date(today.date + 'T12:00:00Z').toLocaleDateString('en-US', {
      weekday: 'long', month: 'long', day: 'numeric'
    })

    let html = `
      <div class="summary-grid">
        <div class="summary-card">
          <div class="summary-number">${today.summary.sessions}</div>
          <div class="summary-label">Sessions Today</div>
        </div>
        <div class="summary-card">
          <div class="summary-number">${today.summary.clients}</div>
          <div class="summary-label">Clients Today</div>
        </div>
      </div>

      <h3 style="margin-bottom: 1rem;">Today — ${todayFmt}</h3>
    `

    if (today.bookings.length === 0) {
      html += '<p class="empty">No sessions scheduled today.</p>'
    } else {
      for (const b of today.bookings) {
        html += renderSessionCard(b)
      }
    }

    // Week overview
    const todayStr = new Date().toISOString().split('T')[0]
    html += '<h3 style="margin: 2rem 0 1rem;">This Week</h3><div class="week-grid">'
    const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    let i = 0
    for (const [date, info] of Object.entries(week.days)) {
      const isToday = date === todayStr
      html += `
        <div class="week-day ${isToday ? 'today' : ''}">
          <div class="week-day-name">${dayNames[i++] || ''}</div>
          <div class="week-day-count">${info.count}</div>
          <div class="week-day-date">${date.slice(5)}</div>
        </div>`
    }
    html += '</div>'

    content.innerHTML = html
    attachSessionActions()
  } catch (err) {
    content.innerHTML = `<p class="empty">Error loading calendar: ${err.message}</p>`
  }
}

function renderSessionCard (b) {
  const statusBadge = b.status === 'confirmed'
    ? '<span style="color:var(--success)">Confirmed</span>'
    : b.status === 'completed'
      ? '<span style="color:var(--text-secondary)">Completed</span>'
      : `<span>${b.status}</span>`

  return `
    <div class="session-card" data-booking-id="${b.id}">
      <div class="session-time">${formatTime(b.startTimeLocal)}</div>
      <div class="session-info">
        <div class="session-client">${esc(b.clientName)}</div>
        <div class="session-meta">${esc(b.packageName)} · ${b.sessionDuration}min · ${statusBadge}</div>
        <div class="session-actions">
          ${b.status === 'confirmed' ? `
            <button class="btn btn-secondary" onclick="completeSession('${b.id}')">Complete</button>
            <button class="btn btn-secondary" onclick="markNoShow('${b.id}')">No-Show</button>
          ` : ''}
          <button class="btn btn-secondary" onclick="cancelSession('${b.id}')">Cancel</button>
        </div>
      </div>
    </div>`
}

function attachSessionActions () {
  // Actions are inline onclick — nothing to attach
}

// ---- Clients ----

async function renderClients () {
  try {
    const clients = await api('GET', '/api/dashboard/clients')

    let html = `<div style="margin-bottom: 1rem;">
      <input type="text" id="client-search" placeholder="Search clients..." style="max-width:300px;" oninput="searchClients(this.value)">
    </div>`

    if (clients.length === 0) {
      html += '<p class="empty">No clients yet. They appear here after their first booking.</p>'
    } else {
      html += '<div id="client-list">'
      for (const c of clients) {
        html += renderClientCard(c)
      }
      html += '</div>'
    }

    content.innerHTML = html
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

function renderClientCard (c) {
  return `
    <div class="client-card" onclick="location.href='/dashboard/clients/${c.id}'">
      <div class="client-card-info">
        <div class="client-card-name">${esc(c.displayName)}</div>
        <div class="client-card-meta">
          ${c.email || 'No email'} · ${c.bookingCount || 0} bookings
          ${c.nextSession ? ' · Next: ' + c.nextSession : ''}
        </div>
      </div>
      <span style="color:var(--text-secondary)">&rarr;</span>
    </div>`
}

async function searchClients (q) {
  const clients = await api('GET', '/api/dashboard/clients?q=' + encodeURIComponent(q))
  const list = document.getElementById('client-list')
  if (list) {
    list.innerHTML = clients.length
      ? clients.map(renderClientCard).join('')
      : '<p class="empty">No matches</p>'
  }
}

// ---- Client Detail ----

async function renderClientDetail (clientId) {
  try {
    const data = await api('GET', '/api/dashboard/clients/' + clientId)
    const { client, bookings } = data

    const upcoming = bookings.filter(b => b.status === 'confirmed')
    const past = bookings.filter(b => b.status === 'completed')

    let html = `
      <p><a href="/dashboard/clients">&larr; Back to Clients</a></p>
      <h2 style="margin: 1rem 0;">${esc(client.displayName)}</h2>
      <div class="card">
        <p><strong>Email:</strong> ${esc(client.email || 'Not provided')}</p>
        <p><strong>Client since:</strong> ${new Date(client.createdAt).toLocaleDateString()}</p>
      </div>

      <h3 style="margin: 1.5rem 0 1rem;">Upcoming (${upcoming.length})</h3>
    `
    if (upcoming.length === 0) {
      html += '<p class="empty">No upcoming sessions</p>'
    } else {
      for (const b of upcoming) {
        html += `<div class="session-card">
          <div class="session-time">${b.date}<br>${formatTime(b.startTimeLocal)}</div>
          <div class="session-info">
            <div class="session-meta">${esc(b.packageName)}</div>
          </div>
        </div>`
      }
    }

    html += `<h3 style="margin: 1.5rem 0 1rem;">Past Sessions (${past.length})</h3>`
    if (past.length === 0) {
      html += '<p class="empty">No past sessions</p>'
    } else {
      for (const b of past) {
        html += `<div class="session-card">
          <div class="session-time">${b.date}</div>
          <div class="session-info">
            <div class="session-meta">${esc(b.packageName)}</div>
            ${b.notes ? `<p style="font-size:0.85rem;color:var(--text-secondary);margin-top:0.25rem;">"${esc(b.notes)}"</p>` : ''}
          </div>
        </div>`
      }
    }

    content.innerHTML = html
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

// ---- Packages ----

async function renderPackages () {
  try {
    const provData = await api('GET', '/api/dashboard/provider')
    if (!provData.provider) {
      content.innerHTML = '<p class="empty">Create a provider profile first.</p>'
      return
    }
    const provider = provData.provider
    const packages = await api('GET', `/api/providers/${provider.slug}/packages`)

    let html = `
      <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
        <span>${packages.length} package${packages.length !== 1 ? 's' : ''}</span>
        <a href="/dashboard/packages/new" class="btn btn-accent">+ New Package</a>
      </div>
    `

    for (const pkg of packages) {
      const meta = []
      meta.push(pkg.type)
      meta.push(pkg.sessionDuration + 'min')
      if (pkg.sessionCount > 1) meta.push(pkg.sessionCount + ' sessions')
      meta.push(formatPrice(pkg.price))

      html += `
        <div class="package-row">
          <div class="package-row-info">
            <div class="package-row-name">${esc(pkg.name)}</div>
            <div class="package-row-meta">${meta.join(' · ')}</div>
          </div>
          <div class="package-row-actions">
            <a href="/dashboard/packages/${pkg.id}/edit" class="btn btn-secondary">Edit</a>
            <button class="btn btn-secondary" onclick="archivePackage('${pkg.id}')">Archive</button>
          </div>
        </div>`
    }

    content.innerHTML = html
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

// ---- Package Edit ----

async function renderPackageEdit (packageId) {
  let pkg = null
  let myProvider = null

  try {
    const provData = await api('GET', '/api/dashboard/provider')
    myProvider = provData.provider
    if (packageId) {
      pkg = await api('GET', `/api/packages/${packageId}`)
    }
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
    return
  }

  const isNew = !pkg
  const providerId = pkg ? pkg.providerId : (myProvider ? myProvider.id : '')

  content.innerHTML = `
    <p><a href="/dashboard/packages">&larr; Back to Packages</a></p>
    <h2 style="margin:1rem 0;">${isNew ? 'New Package' : 'Edit Package'}</h2>
    <form id="package-form" class="card">
      <input type="hidden" id="pkg-providerId" value="${providerId}">
      <label>Package Type</label>
      <select id="pkg-type">
        <option value="discovery" ${pkg && pkg.type === 'discovery' ? 'selected' : ''}>Discovery (Free)</option>
        <option value="single" ${pkg && pkg.type === 'single' ? 'selected' : ''}>Single Session</option>
        <option value="bundle" ${pkg && pkg.type === 'bundle' ? 'selected' : ''}>Bundle (Multiple Sessions)</option>
      </select>
      <label>Name *</label>
      <input type="text" id="pkg-name" value="${esc(pkg ? pkg.name : '')}" required>
      <label>Description</label>
      <textarea id="pkg-description" rows="3">${esc(pkg ? pkg.description : '')}</textarea>
      <label>Session Duration (minutes)</label>
      <input type="number" id="pkg-duration" value="${pkg ? pkg.sessionDuration : 60}" min="5" step="5">
      <div id="price-section">
        <label>Price (cents)</label>
        <input type="number" id="pkg-price" value="${pkg ? pkg.price : 0}" min="0">
        <p style="font-size:0.8rem;color:var(--text-secondary)">Enter in cents: 7500 = $75.00</p>
      </div>
      <div id="bundle-section" style="display:none;">
        <label>Number of Sessions</label>
        <input type="number" id="pkg-sessionCount" value="${pkg ? pkg.sessionCount : 6}" min="2">
        <label>Pay-in-Full Discount (%)</label>
        <input type="number" id="pkg-discount" value="${pkg ? pkg.discountFullPay || 0 : 0}" min="0" max="50">
        <label>Installments</label>
        <input type="number" id="pkg-installments" value="${pkg ? pkg.installments || 0 : 0}" min="0" max="12">
      </div>
      <div style="margin-top:1rem;display:flex;gap:0.5rem;">
        <button type="submit" class="btn btn-accent">${isNew ? 'Create Package' : 'Save Changes'}</button>
        <a href="/dashboard/packages" class="btn btn-secondary">Cancel</a>
      </div>
    </form>
    <script>
      // Show/hide fields based on type
      const typeSelect = document.getElementById('pkg-type')
      function updateTypeFields() {
        const t = typeSelect.value
        document.getElementById('price-section').style.display = t === 'discovery' ? 'none' : ''
        document.getElementById('bundle-section').style.display = t === 'bundle' ? '' : 'none'
        if (t === 'discovery') document.getElementById('pkg-price').value = '0'
      }
      typeSelect.addEventListener('change', updateTypeFields)
      updateTypeFields()

      document.getElementById('package-form').addEventListener('submit', async (e) => {
        e.preventDefault()
        const body = {
          providerId: document.getElementById('pkg-providerId').value,
          name: document.getElementById('pkg-name').value,
          type: typeSelect.value,
          description: document.getElementById('pkg-description').value,
          sessionDuration: parseInt(document.getElementById('pkg-duration').value),
          price: parseInt(document.getElementById('pkg-price').value),
          sessionCount: parseInt(document.getElementById('pkg-sessionCount').value) || 1,
          discountFullPay: parseInt(document.getElementById('pkg-discount').value) || 0,
          installments: parseInt(document.getElementById('pkg-installments').value) || 0
        }
        try {
          if (${packageId ? 'true' : 'false'}) {
            await api('PUT', '/api/packages/${packageId || ''}', body)
            showToast('Package updated', 'success')
          } else {
            await api('POST', '/api/packages', body)
            showToast('Package created', 'success')
          }
          location.href = '/dashboard/packages'
        } catch (err) {
          showToast(err.message, 'error')
        }
      })
    </script>
  `
}

// ---- Availability ----

async function renderAvailability () {
  try {
    const provData = await api('GET', '/api/dashboard/provider')
    if (!provData.provider) {
      content.innerHTML = '<p class="empty">Create a provider profile first.</p>'
      return
    }
    const provider = provData.provider
    const avail = await api('GET', `/api/providers/${provider.slug}/availability`)

    const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
    const timeOpts = generateTimeOptions()

    let html = '<form id="avail-form">'

    for (const day of days) {
      const windows = (avail.weekly && avail.weekly[day]) || []
      const checked = windows.length > 0

      html += `
        <div class="day-editor" data-day="${day}">
          <div class="day-editor-header">
            <input type="checkbox" id="day-${day}" ${checked ? 'checked' : ''} onchange="toggleDay('${day}', this.checked)">
            <label for="day-${day}">${day.charAt(0).toUpperCase() + day.slice(1)}</label>
          </div>
          <div id="windows-${day}" style="${checked ? '' : 'display:none'}">
            ${windows.map((w, i) => renderTimeWindow(day, i, w, timeOpts)).join('')}
            <button type="button" class="add-window" onclick="addWindow('${day}')">+ Add window</button>
          </div>
        </div>`
    }

    html += `
      <div style="margin-top:1.5rem; padding-top:1rem; border-top:1px solid var(--border);">
        <label>Buffer between sessions</label>
        <select id="avail-buffer" style="width:auto;display:inline-block;margin-left:0.5rem;">
          ${[0, 5, 10, 15, 20, 30].map(m => `<option value="${m}" ${avail.bufferMinutes === m ? 'selected' : ''}>${m} min</option>`).join('')}
        </select>
        <div style="margin-top:1rem;">
          <label>Min advance booking</label>
          <select id="avail-minHours" style="width:auto;display:inline-block;margin-left:0.5rem;">
            ${[0, 1, 2, 4, 8, 12, 24, 48].map(h => `<option value="${h}" ${avail.advanceBooking && avail.advanceBooking.minHours === h ? 'selected' : ''}>${h}h</option>`).join('')}
          </select>
        </div>
        <div style="margin-top:1rem;">
          <label>Max advance booking</label>
          <select id="avail-maxDays" style="width:auto;display:inline-block;margin-left:0.5rem;">
            ${[7, 14, 30, 60, 90].map(d => `<option value="${d}" ${avail.advanceBooking && avail.advanceBooking.maxDays === d ? 'selected' : ''}>${d} days</option>`).join('')}
          </select>
        </div>
        <button type="submit" class="btn btn-accent" style="margin-top:1.5rem;">Save Schedule</button>
      </div>
    </form>`

    content.innerHTML = html

    document.getElementById('avail-form').addEventListener('submit', async (e) => {
      e.preventDefault()
      const weekly = {}
      for (const day of days) {
        const cb = document.getElementById('day-' + day)
        if (!cb.checked) { weekly[day] = []; continue }
        const windowEls = document.querySelectorAll('#windows-' + day + ' .time-window')
        weekly[day] = Array.from(windowEls).map(el => ({
          start: el.querySelector('.tw-start').value,
          end: el.querySelector('.tw-end').value
        }))
      }

      try {
        await api('PUT', '/api/availability', {
          providerId: provider.id,
          timezone: provider.timezone,
          weekly,
          bufferMinutes: parseInt(document.getElementById('avail-buffer').value),
          advanceBooking: {
            minHours: parseInt(document.getElementById('avail-minHours').value),
            maxDays: parseInt(document.getElementById('avail-maxDays').value)
          }
        })
        showToast('Schedule saved', 'success')
      } catch (err) {
        showToast(err.message, 'error')
      }
    })
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

function renderTimeWindow (day, index, window, timeOpts) {
  return `
    <div class="time-window">
      <select class="tw-start">${timeOpts.map(t => `<option value="${t}" ${window.start === t ? 'selected' : ''}>${t}</option>`).join('')}</select>
      <span>to</span>
      <select class="tw-end">${timeOpts.map(t => `<option value="${t}" ${window.end === t ? 'selected' : ''}>${t}</option>`).join('')}</select>
      <button type="button" class="remove-window" onclick="this.parentElement.remove()">&times;</button>
    </div>`
}

function generateTimeOptions () {
  const opts = []
  for (let h = 0; h < 24; h++) {
    for (let m = 0; m < 60; m += 15) {
      opts.push(String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0'))
    }
  }
  return opts
}

// ---- Analytics ----

async function renderAnalytics () {
  try {
    const data = await api('GET', '/api/dashboard/analytics')

    let html = `
      <div class="summary-grid">
        <div class="summary-card">
          <div class="summary-number">${data.sessions.completed}</div>
          <div class="summary-label">Completed</div>
        </div>
        <div class="summary-card">
          <div class="summary-number">${data.sessions.confirmed}</div>
          <div class="summary-label">Upcoming</div>
        </div>
        <div class="summary-card">
          <div class="summary-number">${data.sessions.cancelled}</div>
          <div class="summary-label">Cancelled</div>
        </div>
        <div class="summary-card">
          <div class="summary-number">${data.clients}</div>
          <div class="summary-label">Clients</div>
        </div>
      </div>

      <h3 style="margin:1.5rem 0 1rem;">By Package</h3>
    `

    for (const [pkgId, info] of Object.entries(data.packages)) {
      html += `
        <div class="package-row">
          <div class="package-row-info">
            <div class="package-row-name">${esc(info.name)}</div>
            <div class="package-row-meta">${info.count} bookings · ${formatPrice(info.price)} each</div>
          </div>
        </div>`
    }

    content.innerHTML = html
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

// ---- Settings ----

async function renderSettings () {
  try {
    const provData = await api('GET', '/api/dashboard/provider')
    if (!provData.provider) {
      content.innerHTML = '<p class="empty">No provider profile found.</p>'
      return
    }
    const p = provData.provider
    const avail = await api('GET', `/api/providers/${p.slug}/availability`)

    const pubPage = PATH_PREFIX + '/' + p.slug

    content.innerHTML = `
      <div class="settings-grid">

        <div class="settings-section">
          <h3>Profile</h3>
          <form id="profile-form">
            <label>Display Name</label>
            <input type="text" id="set-name" value="${esc(p.displayName)}">
            <label>Bio</label>
            <textarea id="set-bio" rows="3">${esc(p.bio)}</textarea>
            <label>Specialties (comma-separated)</label>
            <input type="text" id="set-specialties" value="${(p.specialties || []).join(', ')}">
            <div class="settings-row">
              <div>
                <label>Accent Color</label>
                <input type="color" id="set-color" value="${p.accentColor || '#c4a265'}" style="width:50px;height:34px;padding:2px;">
              </div>
              <div style="flex:2;">
                <label>Timezone</label>
                <input type="text" value="${esc(p.timezone || 'America/New_York')}" disabled style="background:#f8f6f2;">
              </div>
            </div>
            <button type="submit" class="btn btn-accent" style="margin-top:0.25rem;">Save Profile</button>
          </form>
        </div>

        <div class="settings-section">
          <h3>Booking Page</h3>
          <div class="settings-info">
            <p>Your public URL:</p>
            <p style="margin:0.5rem 0;"><a href="${pubPage}" target="_blank" style="font-size:1rem;">${pubPage}</a></p>
            <p>Status: <span class="settings-badge ${p.active ? 'success' : 'neutral'}">${p.active ? 'Published' : 'Unpublished'}</span></p>
          </div>

          <h3 style="margin-top:1.25rem;">Cancellation Policy</h3>
          <form id="policy-form">
            <div class="settings-row">
              <div>
                <label>Window</label>
                <select id="set-cancelWindow" style="width:auto;">
                  ${[0, 12, 24, 48, 72].map(h => `<option value="${h}" ${avail.cancellation && avail.cancellation.windowHours === h ? 'selected' : ''}>${h}h before</option>`).join('')}
                </select>
              </div>
              <div>
                <label>Refund</label>
                <select id="set-refundPolicy" style="width:auto;">
                  <option value="full" ${avail.cancellation && avail.cancellation.refundPolicy === 'full' ? 'selected' : ''}>Full refund</option>
                  <option value="partial" ${avail.cancellation && avail.cancellation.refundPolicy === 'partial' ? 'selected' : ''}>50% refund</option>
                  <option value="none" ${avail.cancellation && avail.cancellation.refundPolicy === 'none' ? 'selected' : ''}>No refund</option>
                </select>
              </div>
            </div>
            <button type="submit" class="btn btn-accent">Save Policy</button>
          </form>
        </div>

        <div class="settings-section">
          <h3>Google Calendar</h3>
          <div id="gcal-section"><p class="settings-info">Loading...</p></div>
        </div>

        <div class="settings-section">
          <h3>Identity</h3>
          <p class="settings-info" style="word-break:break-all;">
            Public Key:<br>
            <code style="font-size:0.75rem;">${HyperBookingIdentity.getPublicKey() || 'Not signed in'}</code>
          </p>
          <div style="display:flex;gap:0.5rem;margin-top:0.75rem;">
            <button class="btn btn-secondary" onclick="exportKeypair()">Export Keypair</button>
            <button class="btn btn-danger" onclick="signOut()">Sign Out</button>
          </div>
        </div>

      </div>
    `

    document.getElementById('profile-form').addEventListener('submit', async (e) => {
      e.preventDefault()
      try {
        await api('PUT', `/api/providers/${p.slug}`, {
          displayName: document.getElementById('set-name').value,
          bio: document.getElementById('set-bio').value,
          specialties: document.getElementById('set-specialties').value.split(',').map(s => s.trim()).filter(Boolean),
          accentColor: document.getElementById('set-color').value
        })
        showToast('Profile saved', 'success')
      } catch (err) { showToast(err.message, 'error') }
    })

    document.getElementById('policy-form').addEventListener('submit', async (e) => {
      e.preventDefault()
      try {
        await api('PUT', '/api/availability', {
          providerId: p.id,
          timezone: p.timezone,
          weekly: avail.weekly,
          bufferMinutes: avail.bufferMinutes,
          advanceBooking: avail.advanceBooking,
          cancellation: {
            windowHours: parseInt(document.getElementById('set-cancelWindow').value),
            refundPolicy: document.getElementById('set-refundPolicy').value
          }
        })
        showToast('Policy saved', 'success')
      } catch (err) { showToast(err.message, 'error') }
    })

    // Google Calendar section
    loadGcalSection()
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

async function loadGcalSection () {
  const gcalEl = document.getElementById('gcal-section')
  if (!gcalEl) return

  try {
    const status = await api('GET', '/api/gcal/status')

    if (!status.configured) {
      // Server doesn't have Google credentials set up
      gcalEl.innerHTML = `
        <p style="color:var(--text-secondary);">
          To enable Google Calendar sync, set <code>GCAL_CLIENT_ID</code> and
          <code>GCAL_CLIENT_SECRET</code> environment variables when starting the server.
        </p>`
      return
    }

    if (status.connected) {
      // Connected — show status, calendar picker, disconnect
      gcalEl.innerHTML = `
        <div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;">
          <span class="settings-badge success">Connected</span>
          <span class="settings-info" style="margin:0;">Events block booking slots automatically</span>
        </div>
        <p style="font-size:0.8rem;color:var(--text-secondary);margin-bottom:0.75rem;">
          Only availability (busy/free) is read. Event details are never accessed.
        </p>
        <div id="gcal-calendars">
          <p class="settings-info">Loading calendars...</p>
        </div>
        <div style="margin-top:0.75rem;">
          <button class="btn btn-danger" id="gcal-disconnect-btn" style="font-size:0.8rem;padding:0.4rem 0.8rem;">Disconnect</button>
        </div>
      `

      // Load calendar picker
      try {
        const calendars = await api('GET', '/api/gcal/calendars')
        const selectedIds = status.calendarIds || ['primary']
        const calDiv = document.getElementById('gcal-calendars')
        if (calDiv && Array.isArray(calendars) && calendars.length > 0) {
          calDiv.innerHTML = `
            <p style="font-size:0.8rem;font-weight:500;margin-bottom:0.4rem;color:var(--text);">Monitor for conflicts:</p>
            <div style="max-height:240px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:0.25rem 0;">
              ${calendars.map(cal => `
                <label style="display:flex;align-items:center;gap:0.5rem;padding:0.35rem 0.75rem;cursor:pointer;font-size:0.85rem;border-bottom:1px solid #f0f0f0;">
                  <input type="checkbox" class="gcal-cal-check" value="${cal.id}"
                    ${selectedIds.includes(cal.id) ? 'checked' : ''}
                    style="width:auto;margin:0;flex-shrink:0;">
                  <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(cal.summary)}${cal.primary ? ' <em style="color:var(--text-secondary);">(primary)</em>' : ''}</span>
                </label>
              `).join('')}
            </div>
            <button class="btn btn-accent" id="gcal-save-cals-btn" style="margin-top:0.5rem;font-size:0.8rem;padding:0.4rem 0.8rem;">Save Selection</button>
          `
          document.getElementById('gcal-save-cals-btn').addEventListener('click', async () => {
            const checked = [...document.querySelectorAll('.gcal-cal-check:checked')].map(el => el.value)
            if (checked.length === 0) { showToast('Select at least one calendar', 'error'); return }
            try {
              await api('PUT', '/api/gcal/calendars', { calendarIds: checked })
              showToast('Calendar selection saved', 'success')
            } catch (err) { showToast(err.message, 'error') }
          })
        }
      } catch (err) {
        const calDiv = document.getElementById('gcal-calendars')
        if (calDiv) calDiv.innerHTML = '<p style="font-size:0.85rem;color:var(--text-secondary);">Could not load calendar list.</p>'
      }

      document.getElementById('gcal-disconnect-btn').addEventListener('click', async () => {
        if (!confirm('Disconnect Google Calendar?')) return
        try {
          await api('DELETE', '/api/gcal/disconnect')
          showToast('Disconnected', 'success')
          loadGcalSection()
        } catch (err) { showToast(err.message, 'error') }
      })
    } else {
      // Not connected — one button to connect
      gcalEl.innerHTML = `
        <p style="color:var(--text-secondary);margin-bottom:1rem;">
          Sync your Google Calendar so clients only see times when you're actually free.
        </p>
        <button class="btn btn-accent" id="gcal-connect-btn">Connect Google Calendar</button>
      `
      document.getElementById('gcal-connect-btn').addEventListener('click', async () => {
        try {
          const { url } = await api('GET', '/api/gcal/auth-url')
          window.location.href = url
        } catch (err) { showToast(err.message, 'error') }
      })
    }
  } catch (err) {
    gcalEl.innerHTML = '<p style="color:var(--text-secondary);">Could not load calendar status.</p>'
  }
}

// ---- Profile ----

async function renderProfile () {
  const hasPk = HyperBookingIdentity.hasKeypair()

  if (!hasPk) {
    content.innerHTML = `
      <div class="card" style="text-align:center;padding:2rem;">
        <p>You are not signed in.</p>
        <button class="btn btn-accent" onclick="signIn()" style="margin-top:1rem;">Create Identity</button>
      </div>`
    return
  }

  let profile = {}
  try {
    profile = await api('GET', '/api/profile')
  } catch {}

  content.innerHTML = `
    <div class="card">
      <h3 style="margin-bottom:1rem;">Your Profile</h3>
      <form id="profile-form">
        <label>Display Name</label>
        <input type="text" id="prof-name" value="${esc(profile.displayName || '')}" placeholder="Your name">
        <label>Email (for notifications)</label>
        <input type="email" id="prof-email" value="${esc(profile.email || '')}" placeholder="you@email.com">
        <label>Phone (for SMS reminders, optional)</label>
        <input type="tel" id="prof-phone" value="${esc(profile.phone || '')}" placeholder="+1 555 123 4567">
        <button type="submit" class="btn btn-accent">Save Profile</button>
      </form>
    </div>

    <div class="card" style="margin-top:1rem;">
      <h3 style="margin-bottom:1rem;">Identity</h3>
      <p style="font-size:0.85rem;color:var(--text-secondary);word-break:break-all;">
        Public Key: ${HyperBookingIdentity.getPublicKey()}
      </p>
      <div style="margin-top:1rem;display:flex;gap:0.5rem;flex-wrap:wrap;">
        <button class="btn btn-secondary" onclick="exportKeypair()">Export Keypair</button>
        <button class="btn btn-secondary" onclick="importKeypairPrompt()">Import Keypair</button>
        <button class="btn btn-danger" onclick="signOut()">Sign Out</button>
      </div>
    </div>
  `

  document.getElementById('profile-form').addEventListener('submit', async (e) => {
    e.preventDefault()
    try {
      await api('PUT', '/api/profile', {
        displayName: document.getElementById('prof-name').value,
        email: document.getElementById('prof-email').value,
        phone: document.getElementById('prof-phone').value
      })
      showToast('Profile saved', 'success')
      // Update nav display name
      const nameEl = document.getElementById('user-name')
      if (nameEl) nameEl.textContent = document.getElementById('prof-name').value
    } catch (err) {
      showToast(err.message, 'error')
    }
  })
}

// ---- Portal ----

async function renderPortal () {
  try {
    const bookings = await api('GET', '/api/portal/bookings')
    const today = new Date().toISOString().split('T')[0]
    const upcoming = bookings.filter(b => b.status === 'confirmed' && b.date >= today)
    const past = bookings.filter(b => b.status === 'completed' || b.date < today)

    let html = '<h3 style="margin-bottom:1rem;">Upcoming Sessions</h3>'

    if (upcoming.length === 0) {
      html += '<p class="empty">No upcoming sessions.</p>'
    } else {
      for (const b of upcoming) {
        html += `
          <div class="session-card">
            <div class="session-time">${b.date}<br>${formatTime(b.startTimeLocal)}</div>
            <div class="session-info">
              <div class="session-meta">${esc(b.packageId)}</div>
              <div class="session-actions">
                <button class="btn btn-secondary" onclick="cancelSession('${b.id}')">Cancel</button>
              </div>
            </div>
          </div>`
      }
    }

    html += `<h3 style="margin:2rem 0 1rem;">Past Sessions (${past.length})</h3>`
    if (past.length === 0) {
      html += '<p class="empty">No past sessions.</p>'
    } else {
      for (const b of past) {
        html += `
          <div class="session-card">
            <div class="session-time">${b.date}</div>
            <div class="session-info">
              <div class="session-meta">${b.status}${b.notes ? ' — "' + esc(b.notes) + '"' : ''}</div>
            </div>
          </div>`
      }
    }

    content.innerHTML = html
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

// ---- Admin ----

// ---- Create Provider ----

function renderCreateProvider () {
  content.innerHTML = `
    <div class="card" style="max-width:600px;margin:0 auto;padding:2rem;">
      <h2 style="margin-bottom:0.5rem;">Create Your Coaching Page</h2>
      <p style="color:var(--text-secondary);margin-bottom:1.5rem;">
        Set up your provider profile so clients can discover and book sessions with you.
      </p>

      <form id="create-provider-form">
        <label for="cp-name">Display Name *</label>
        <input type="text" id="cp-name" placeholder="e.g. Fleeky Flanco" required>

        <label for="cp-slug" style="margin-top:1rem;">URL Slug *</label>
        <input type="text" id="cp-slug" placeholder="e.g. fleeky-flanco" required
          pattern="[a-z0-9][a-z0-9\\-]*[a-z0-9]" title="Lowercase letters, numbers, and hyphens only">
        <p style="font-size:0.8rem;color:var(--text-secondary);margin:0.25rem 0 0;">
          Your public page will be at <strong>/${document.getElementById('cp-slug')?.value || 'your-slug'}</strong>
        </p>

        <label for="cp-bio" style="margin-top:1rem;">Bio</label>
        <textarea id="cp-bio" rows="4" placeholder="Tell clients about yourself, your approach, and your experience..."></textarea>

        <label for="cp-specialties" style="margin-top:1rem;">Specialties</label>
        <input type="text" id="cp-specialties" placeholder="e.g. Handstands, Acrobatics, Movement (comma-separated)">

        <label for="cp-timezone" style="margin-top:1rem;">Timezone</label>
        <select id="cp-timezone">
          <option value="America/New_York">Eastern (New York)</option>
          <option value="America/Chicago">Central (Chicago)</option>
          <option value="America/Denver">Mountain (Denver)</option>
          <option value="America/Los_Angeles">Pacific (Los Angeles)</option>
          <option value="UTC">UTC</option>
          <option value="Europe/London">London</option>
          <option value="Europe/Paris">Paris</option>
          <option value="Asia/Tokyo">Tokyo</option>
        </select>

        <label for="cp-color" style="margin-top:1rem;">Accent Color</label>
        <input type="color" id="cp-color" value="#c4a265" style="width:60px;height:36px;border:none;cursor:pointer;">

        <div style="margin-top:1.5rem;">
          <button type="submit" class="btn btn-accent" id="cp-submit">Create My Page</button>
        </div>

        <p id="cp-error" style="color:var(--error);margin-top:0.75rem;display:none;"></p>
      </form>
    </div>
  `

  // Auto-generate slug from name
  const nameInput = document.getElementById('cp-name')
  const slugInput = document.getElementById('cp-slug')
  nameInput.addEventListener('input', () => {
    if (!slugInput.dataset.manual) {
      slugInput.value = nameInput.value
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-|-$/g, '')
    }
  })
  slugInput.addEventListener('input', () => {
    slugInput.dataset.manual = 'true'
  })

  document.getElementById('create-provider-form').addEventListener('submit', async (e) => {
    e.preventDefault()
    const errEl = document.getElementById('cp-error')
    errEl.style.display = 'none'

    const name = nameInput.value.trim()
    const slug = slugInput.value.trim()
    if (!name || !slug) {
      errEl.textContent = 'Name and slug are required.'
      errEl.style.display = ''
      return
    }

    const submitBtn = document.getElementById('cp-submit')
    submitBtn.disabled = true
    submitBtn.textContent = 'Creating...'

    try {
      await api('POST', '/api/providers', {
        displayName: name,
        slug: slug,
        bio: document.getElementById('cp-bio').value.trim(),
        specialties: document.getElementById('cp-specialties').value
          .split(',').map(s => s.trim()).filter(Boolean),
        timezone: document.getElementById('cp-timezone').value,
        accentColor: document.getElementById('cp-color').value
      })

      showToast('Your coaching page is live!', 'success')
      // Reload dashboard — now they have a provider
      setTimeout(() => location.reload(), 500)
    } catch (err) {
      errEl.textContent = err.message
      errEl.style.display = ''
      submitBtn.disabled = false
      submitBtn.textContent = 'Create My Page'
    }
  })
}

// ---- Admin ----

async function renderAdmin () {
  try {
    const sys = await api('GET', '/api/admin/system')
    content.innerHTML = `
      <div class="summary-grid">
        <div class="summary-card">
          <div class="summary-number">${sys.users}</div>
          <div class="summary-label">Users</div>
        </div>
        <div class="summary-card">
          <div class="summary-number">${sys.providers}</div>
          <div class="summary-label">Providers</div>
        </div>
        <div class="summary-card">
          <div class="summary-number">${sys.bookings}</div>
          <div class="summary-label">Bookings</div>
        </div>
        <div class="summary-card">
          <div class="summary-number">${sys.gcalConfigured ? 'Yes' : 'No'}</div>
          <div class="summary-label">Google Calendar</div>
        </div>
      </div>
      <p style="font-size:0.85rem;color:var(--text-secondary);margin-top:0.5rem;">
        v${sys.version} · uptime ${Math.floor(sys.uptime / 60)}m
      </p>
      <h3 style="margin:1.5rem 0 1rem;">Manage</h3>
      <div style="display:flex;gap:0.5rem;flex-wrap:wrap;">
        <a href="${PATH_PREFIX}/admin/providers" class="btn btn-secondary">Providers</a>
        <a href="${PATH_PREFIX}/admin/users" class="btn btn-secondary">Users</a>
      </div>
    `
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

async function renderAdminProviders () {
  try {
    const [providers, users] = await Promise.all([
      api('GET', '/api/admin/providers'),
      api('GET', '/api/admin/users')
    ])

    let html = `<p><a href="${PATH_PREFIX}/admin">&larr; Back to Admin</a></p>`
    html += `<h3 style="margin:1rem 0;">Providers (${providers.length})</h3>`

    for (const p of providers) {
      const ownerLabel = p.owner
        ? esc(p.owner.username || p.owner.name || p.owner.userId.substring(0, 12) + '...')
        : '<em style="color:var(--text-secondary)">unassigned</em>'

      html += `
        <div class="package-row" style="flex-wrap:wrap;gap:0.5rem;">
          <div class="package-row-info" style="min-width:200px;">
            <div class="package-row-name">${esc(p.displayName)}</div>
            <div class="package-row-meta">
              /${esc(p.slug)} · ${p.active ? '<span style="color:var(--success)">Active</span>' : '<span style="color:var(--error)">Disabled</span>'}
              · ${p.bookingCount} bookings
              ${p.gcalConnected ? ' · <span style="color:var(--success)">GCal</span>' : ''}
            </div>
            <div class="package-row-meta" style="margin-top:0.25rem;">
              Owner: ${ownerLabel}
            </div>
          </div>
          <div class="package-row-actions" style="display:flex;gap:0.25rem;flex-wrap:wrap;">
            <a href="${PATH_PREFIX}/${p.slug}" class="btn btn-secondary" target="_blank" style="font-size:0.8rem;">View</a>
            <button class="btn btn-secondary" style="font-size:0.8rem;" onclick="adminToggleProvider('${p.id}', ${p.active})">${p.active ? 'Disable' : 'Enable'}</button>
            ${p.userId ? `<button class="btn btn-secondary" style="font-size:0.8rem;" onclick="adminUnlinkProvider('${p.id}', '${esc(p.displayName)}')">Unlink Owner</button>` : ''}
            ${!p.userId ? `<button class="btn btn-accent" style="font-size:0.8rem;" onclick="adminShowAssign('${p.id}')">Assign</button>` : ''}
          </div>
        </div>`

      // Hidden assign form
      if (!p.userId) {
        html += `
          <div id="assign-${p.id}" style="display:none;padding:0.75rem 1rem;background:var(--surface);border-radius:8px;margin:-0.5rem 0 0.5rem;">
            <label style="font-size:0.85rem;">Assign to user:</label>
            <select id="assign-select-${p.id}" style="margin:0.25rem 0;">
              ${users.filter(u => !u.provider).map(u =>
                `<option value="${u.userId}">${esc(u.username || u.name || u.userId.substring(0, 16))}</option>`
              ).join('')}
            </select>
            <button class="btn btn-accent" style="font-size:0.8rem;" onclick="adminAssignProvider('${p.id}')">Assign</button>
            <button class="btn btn-secondary" style="font-size:0.8rem;" onclick="document.getElementById('assign-${p.id}').style.display='none'">Cancel</button>
          </div>`
      }
    }

    content.innerHTML = html
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

async function renderAdminUsers () {
  try {
    const users = await api('GET', '/api/admin/users')

    let html = `<p><a href="${PATH_PREFIX}/admin">&larr; Back to Admin</a></p>`
    html += `<h3 style="margin:1rem 0;">Users (${users.length})</h3>`

    for (const u of users) {
      const perms = (u.globalPermissions || []).join(', ')
      const provLabel = u.provider
        ? `<a href="${PATH_PREFIX}/${u.provider.slug}">${esc(u.provider.displayName)}</a>`
        : '<em style="color:var(--text-secondary)">none</em>'
      const pkSnippet = u.identities.length > 0
        ? u.identities[0].publicKey?.substring(0, 20) + '...'
        : 'no identity'

      html += `
        <div class="package-row" style="flex-wrap:wrap;gap:0.5rem;">
          <div class="package-row-info" style="min-width:200px;">
            <div class="package-row-name">${esc(u.username || u.name || 'Anonymous')}</div>
            <div class="package-row-meta">
              ${u.userId.substring(0, 12)}... · ${perms}
            </div>
            <div class="package-row-meta" style="margin-top:0.25rem;">
              Provider: ${provLabel} · Key: <code style="font-size:0.75rem;">${esc(pkSnippet)}</code>
            </div>
          </div>
          <div class="package-row-actions" style="display:flex;gap:0.25rem;flex-wrap:wrap;">
            ${!u.globalPermissions?.includes('admin')
              ? `<button class="btn btn-secondary" style="font-size:0.8rem;" onclick="adminPromote('${u.userId}')">Make Admin</button>`
              : `<span style="font-size:0.8rem;color:var(--success);padding:0.25rem 0.5rem;">Admin</span>`
            }
            ${u.globalPermissions?.includes('admin') && u.userId !== users[0]?.userId
              ? `<button class="btn btn-secondary" style="font-size:0.8rem;" onclick="adminDemote('${u.userId}')">Remove Admin</button>`
              : ''
            }
          </div>
        </div>`
    }

    content.innerHTML = html
  } catch (err) {
    content.innerHTML = `<p class="empty">Error: ${err.message}</p>`
  }
}

// ---- Admin Actions ----

async function adminToggleProvider (providerId, currentlyActive) {
  try {
    const result = await api('PUT', `/api/admin/providers/${providerId}/toggle`)
    showToast(result.active ? 'Provider enabled' : 'Provider disabled', 'success')
    renderAdminProviders()
  } catch (err) { showToast(err.message, 'error') }
}

async function adminUnlinkProvider (providerId, name) {
  if (!confirm(`Unlink owner from "${name}"? The provider page stays but has no owner until reassigned.`)) return
  try {
    await api('PUT', `/api/admin/providers/${providerId}/unlink`)
    showToast('Owner unlinked', 'success')
    renderAdminProviders()
  } catch (err) { showToast(err.message, 'error') }
}

function adminShowAssign (providerId) {
  const el = document.getElementById('assign-' + providerId)
  if (el) el.style.display = ''
}

async function adminAssignProvider (providerId) {
  const select = document.getElementById('assign-select-' + providerId)
  if (!select) return
  try {
    await api('PUT', `/api/admin/providers/${providerId}/assign`, { userId: select.value })
    showToast('Provider assigned', 'success')
    renderAdminProviders()
  } catch (err) { showToast(err.message, 'error') }
}

async function adminPromote (userId) {
  if (!confirm('Grant admin permissions?')) return
  try {
    await api('PUT', `/api/admin/users/${userId}/permissions`, { permissions: ['read', 'write', 'admin'] })
    showToast('User promoted to admin', 'success')
    renderAdminUsers()
  } catch (err) { showToast(err.message, 'error') }
}

async function adminDemote (userId) {
  if (!confirm('Remove admin permissions? User will keep read + write.')) return
  try {
    await api('PUT', `/api/admin/users/${userId}/permissions`, { permissions: ['read', 'write'] })
    showToast('Admin removed', 'success')
    renderAdminUsers()
  } catch (err) { showToast(err.message, 'error') }
}

// ---- Global Actions ----

async function completeSession (bookingId) {
  try {
    await api('PUT', `/api/bookings/${bookingId}/complete`)
    showToast('Session marked complete', 'success')
    renderCalendar()
  } catch (err) { showToast(err.message, 'error') }
}

async function markNoShow (bookingId) {
  if (!confirm('Mark as no-show?')) return
  try {
    await api('PUT', `/api/bookings/${bookingId}/cancel`, { cancelledBy: 'provider' })
    showToast('Marked as no-show', 'success')
    renderCalendar()
  } catch (err) { showToast(err.message, 'error') }
}

async function cancelSession (bookingId) {
  if (!confirm('Cancel this session?')) return
  try {
    await api('PUT', `/api/bookings/${bookingId}/cancel`)
    showToast('Session cancelled', 'success')
    renderCalendar()
  } catch (err) { showToast(err.message, 'error') }
}

async function archivePackage (packageId) {
  if (!confirm('Archive this package?')) return
  try {
    await api('DELETE', `/api/packages/${packageId}`)
    showToast('Package archived', 'success')
    renderPackages()
  } catch (err) { showToast(err.message, 'error') }
}

function exportKeypair () {
  const data = HyperBookingIdentity.exportKeypair()
  if (!data) return showToast('No keypair to export', 'error')
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
  const a = document.createElement('a')
  a.href = URL.createObjectURL(blob)
  a.download = 'hyper-booking-keypair.json'
  a.click()
  showToast('Keypair exported', 'success')
}

function importKeypairPrompt () {
  const input = prompt('Paste your 132-character keypair:')
  if (input) {
    try {
      HyperBookingIdentity.importKeypair(input.trim())
      showToast('Keypair imported', 'success')
      location.reload()
    } catch (err) { showToast(err.message, 'error') }
  }
}

function signOut () {
  if (!confirm('Sign out? Make sure you have exported your keypair first.')) return
  HyperBookingIdentity.clear()
  showToast('Signed out')
  location.href = '/'
}

function esc (s) {
  if (!s) return ''
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}

function toggleDay (day, checked) {
  document.getElementById('windows-' + day).style.display = checked ? '' : 'none'
}

function addWindow (day) {
  const container = document.getElementById('windows-' + day)
  const timeOpts = generateTimeOptions()
  const div = document.createElement('div')
  div.className = 'time-window'
  div.innerHTML = `
    <select class="tw-start">${timeOpts.map(t => `<option value="${t}">${t}</option>`).join('')}</select>
    <span>to</span>
    <select class="tw-end">${timeOpts.map(t => `<option value="${t}" ${t === '17:00' ? 'selected' : ''}>${t}</option>`).join('')}</select>
    <button type="button" class="remove-window" onclick="this.parentElement.remove()">&times;</button>
  `
  container.insertBefore(div, container.querySelector('.add-window'))
}

window.initDashboard = initDashboard
window.signIn = signIn
window.completeSession = completeSession
window.markNoShow = markNoShow
window.cancelSession = cancelSession
window.archivePackage = archivePackage
window.exportKeypair = exportKeypair
window.importKeypairPrompt = importKeypairPrompt
window.signOut = signOut
window.toggleDay = toggleDay
window.addWindow = addWindow
window.searchClients = searchClients