← back to NationalPaperHangers

public/js/calendar-installer.js

110 lines

// Installer admin calendar JS — manage availability, time-off, view upcoming.
(function () {
  function csrf() {
    const m = document.querySelector('meta[name="csrf-token"]');
    return m ? m.getAttribute('content') : '';
  }

  // ---- Availability
  const tbody = document.getElementById('availability-tbody');
  const addAvail = document.getElementById('add-availability');

  if (addAvail) {
    addAvail.addEventListener('submit', async (e) => {
      e.preventDefault();
      const data = Object.fromEntries(new FormData(addAvail).entries());
      const r = await fetch('/api/admin/availability', {
        method: 'POST',
        headers: { 'content-type': 'application/json', 'x-csrf-token': csrf() },
        body: JSON.stringify(data)
      });
      if (!r.ok) { alert('Could not add window'); return; }
      const row = await r.json();
      const tr = document.createElement('tr');
      tr.dataset.id = row.id;
      tr.innerHTML = `<td>${['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][row.day_of_week]}</td><td>${row.start_time}</td><td>${row.end_time}</td><td><button type="button" class="btn btn-ghost btn-sm" data-remove-availability="${row.id}">Remove</button></td>`;
      tbody.appendChild(tr);
      addAvail.reset();
    });
  }

  document.addEventListener('click', async (e) => {
    const btn = e.target.closest('[data-remove-availability]');
    if (!btn) return;
    const id = btn.dataset.removeAvailability;
    if (!confirm('Remove this availability window?')) return;
    const r = await fetch(`/api/admin/availability/${id}`, {
      method: 'DELETE',
      headers: { 'x-csrf-token': csrf() }
    });
    if (r.ok) btn.closest('tr').remove();
  });

  // ---- Time-off
  const offList = document.getElementById('time-off-list');
  const addOff = document.getElementById('add-time-off');

  async function loadTimeOff() {
    if (!offList) return;
    offList.innerHTML = '';
    const r = await fetch('/api/admin/time-off');
    const j = await r.json();
    if (!j.timeOff || !j.timeOff.length) {
      offList.innerHTML = '<li class="muted">No upcoming blocks.</li>';
      return;
    }
    for (const t of j.timeOff) {
      const li = document.createElement('li');
      li.innerHTML = `<span>${new Date(t.start_at).toLocaleString()} → ${new Date(t.end_at).toLocaleString()}</span>${t.reason ? '<span class="muted"> · ' + escapeHtml(t.reason) + '</span>' : ''}<button type="button" class="btn btn-ghost btn-sm" data-remove-off="${t.id}" style="margin-left:auto">Remove</button>`;
      offList.appendChild(li);
    }
  }

  if (addOff) {
    addOff.addEventListener('submit', async (e) => {
      e.preventDefault();
      const data = Object.fromEntries(new FormData(addOff).entries());
      data.start_at = new Date(data.start_at).toISOString();
      data.end_at = new Date(data.end_at).toISOString();
      const r = await fetch('/api/admin/time-off', {
        method: 'POST',
        headers: { 'content-type': 'application/json', 'x-csrf-token': csrf() },
        body: JSON.stringify(data)
      });
      if (!r.ok) { alert('Could not add block'); return; }
      addOff.reset();
      loadTimeOff();
    });
  }

  document.addEventListener('click', async (e) => {
    const btn = e.target.closest('[data-remove-off]');
    if (!btn) return;
    if (!confirm('Remove this block?')) return;
    const r = await fetch(`/api/admin/time-off/${btn.dataset.removeOff}`, {
      method: 'DELETE',
      headers: { 'x-csrf-token': csrf() }
    });
    if (r.ok) loadTimeOff();
  });

  // ---- Upcoming bookings
  async function loadUpcoming() {
    const el = document.getElementById('upcoming-cal-list');
    if (!el) return;
    const r = await fetch('/api/admin/bookings.json');
    const j = await r.json();
    if (!j.bookings || !j.bookings.length) { el.textContent = 'No upcoming bookings.'; return; }
    el.innerHTML = '<ul class="time-off-list">' + j.bookings.map(b =>
      `<li><span><strong>${escapeHtml(b.customer_name)}</strong> · ${new Date(b.scheduled_start).toLocaleString()} · ${escapeHtml((b.project_type || 'consultation').replace(/_/g, ' '))}</span><span class="status-badge status-${b.status}" style="margin-left:auto">${b.status}</span></li>`
    ).join('') + '</ul>';
  }

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

  loadTimeOff();
  loadUpcoming();
})();