<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>LeadDesk Dashboard</title>

<style>

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f5f3; color: #1a1a18; min-height: 100vh; }

  input, button { font-family: inherit; font-size: 14px; outline: none; }

  input { width: 100%; padding: 8px 12px; border: 1px solid #d3d1c7; border-radius: 8px; background: #fff; color: #1a1a18; }

  input:focus { border-color: #888780; }

  button { cursor: pointer; padding: 8px 16px; border: 1px solid #d3d1c7; border-radius: 8px; background: #fff; color: #1a1a18; transition: background 0.15s; }

  button:hover { background: #f1efe8; }

  button:disabled { opacity: 0.4; cursor: not-allowed; }

  .btn-primary { background: #1a1a18; color: #fff; border-color: #1a1a18; }

  .btn-primary:hover { background: #333; }


  /* Login */

  .login-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 1rem; }

  .login-card { background: #fff; border: 1px solid #d3d1c7; border-radius: 12px; padding: 2rem; width: 100%; max-width: 380px; }

  .login-card h1 { font-size: 18px; font-weight: 500; margin-bottom: 4px; }

  .login-card p { font-size: 13px; color: #888780; margin-bottom: 1.5rem; }

  .field { margin-bottom: 12px; }

  .field label { display: block; font-size: 12px; color: #5f5e5a; margin-bottom: 4px; }

  .error-msg { font-size: 12px; color: #a32d2d; margin: 8px 0; }

  .hint { font-size: 11px; color: #b4b2a9; margin-top: 1rem; }


  /* Layout */

  .app { display: flex; min-height: 100vh; }

  .sidebar { width: 220px; background: #fff; border-right: 1px solid #d3d1c7; padding: 1.5rem 1rem; flex-shrink: 0; }

  .sidebar h2 { font-size: 14px; font-weight: 500; margin-bottom: 4px; }

  .sidebar p { font-size: 11px; color: #888780; margin-bottom: 1.5rem; }

  .nav-item { display: block; width: 100%; text-align: left; padding: 7px 10px; border: none; border-radius: 6px; background: none; font-size: 13px; color: #5f5e5a; cursor: pointer; margin-bottom: 2px; }

  .nav-item:hover { background: #f1efe8; color: #1a1a18; }

  .nav-item.active { background: #f1efe8; color: #1a1a18; font-weight: 500; }

  .disconnect { margin-top: auto; font-size: 12px; color: #888780; border: none; background: none; cursor: pointer; padding: 4px 0; }

  .disconnect:hover { color: #a32d2d; }

  .main { flex: 1; padding: 2rem; overflow-y: auto; }

  .page-title { font-size: 18px; font-weight: 500; margin-bottom: 4px; }

  .page-sub { font-size: 12px; color: #888780; margin-bottom: 1.5rem; }


  /* Metrics */

  .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin-bottom: 1.5rem; }

  .metric { background: #f1efe8; border-radius: 8px; padding: 1rem; }

  .metric-label { font-size: 11px; color: #888780; margin-bottom: 4px; }

  .metric-value { font-size: 22px; font-weight: 500; }

  .metric-sub { font-size: 11px; color: #b4b2a9; margin-top: 4px; }


  /* Cards */

  .card { background: #fff; border: 1px solid #d3d1c7; border-radius: 12px; padding: 1rem; margin-bottom: 12px; }

  .card-title { font-size: 12px; font-weight: 500; color: #5f5e5a; margin-bottom: 12px; }


  /* Bar chart */

  .bar-chart { display: flex; align-items: flex-end; gap: 4px; height: 80px; }

  .bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; }

  .bar { width: 100%; background: #378add; border-radius: 3px 3px 0 0; min-height: 2px; }

  .bar-lbl { font-size: 9px; color: #b4b2a9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; text-align: center; }


  /* Mini bar */

  .mini-bar-row { margin-bottom: 8px; }

  .mini-bar-top { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 3px; }

  .mini-bar-track { height: 6px; border-radius: 3px; background: #f1efe8; overflow: hidden; }

  .mini-bar-fill { height: 100%; border-radius: 3px; background: #378add; }


  /* Table */

  .tbl-wrap { overflow-x: auto; }

  table { width: 100%; border-collapse: collapse; font-size: 12px; }

  th { padding: 9px 12px; text-align: left; font-weight: 500; color: #888780; font-size: 11px; border-bottom: 1px solid #d3d1c7; }

  td { padding: 9px 12px; border-bottom: 1px solid #f1efe8; color: #1a1a18; }

  tr:last-child td { border-bottom: none; }


  /* Badge */

  .badge { font-size: 11px; font-weight: 500; padding: 2px 8px; border-radius: 6px; text-transform: capitalize; display: inline-block; }

  .badge-answered { background: #eaf3de; color: #3b6d11; }

  .badge-missed { background: #fcebeb; color: #a32d2d; }

  .badge-voicemail { background: #faeeda; color: #854f0b; }

  .badge-abandoned { background: #fbeaf0; color: #993556; }

  .badge-default { background: #f1efe8; color: #5f5e5a; }


  /* Grid */

  .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

  @media (max-width: 700px) { .grid-2 { grid-template-columns: 1fr; } .sidebar { display: none; } }


  /* Loading */

  .loading { text-align: center; padding: 3rem; color: #888780; font-size: 14px; }

</style>

</head>

<body>


<div id="root"></div>


<script>

const PROXY = "https://rough-frost-3472.olfr.workers.dev";

let token = null;

let dashData = {};

let currentTab = "overview";


function fmt(n) { return typeof n === "number" ? n.toLocaleString() : n ?? "—"; }

function pct(n) { return typeof n === "number" ? n.toFixed(1) + "%" : "—"; }

function dur(s) { if (!s) return "0s"; const m = Math.floor(s/60), sec = s%60; return m > 0 ? `${m}m ${sec}s` : `${sec}s`; }

function badgeClass(s) {

  const map = { answered: "answered", missed: "missed", voicemail: "voicemail", abandoned: "abandoned" };

  return "badge badge-" + (map[(s||"").toLowerCase()] || "default");

}


function apiFetch(path, opts = {}) {

  return fetch(PROXY + path, {

    ...opts,

    headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${token}`, ...(opts.headers || {}) }

  }).then(r => r.json());

}


async function doLogin() {

  const clientId = document.getElementById("clientId").value.trim();

  const clientSecret = document.getElementById("clientSecret").value.trim();

  const customerId = document.getElementById("customerId").value.trim();

  const errEl = document.getElementById("loginError");

  errEl.textContent = "";


  if (!clientId || !clientSecret || !customerId) { errEl.textContent = "Please fill in all fields."; return; }


  document.getElementById("loginBtn").disabled = true;

  document.getElementById("loginBtn").textContent = "Connecting…";


  try {

    const res = await fetch(PROXY + "/oauth/access-token", {

      method: "POST",

      headers: { "Content-Type": "application/json", Accept: "application/json" },

      body: JSON.stringify({ grant_type: "leaddesk_client_id", client_id: clientId, client_secret: clientSecret, leaddesk_client_id: parseInt(customerId) })

    });

    const j = await res.json();

    if (!res.ok) throw new Error(j.error_description || j.error || "Authentication failed");

    token = j.access_token;

    renderLoading();

    await fetchAll();

  } catch(e) {

    errEl.textContent = e.message;

    document.getElementById("loginBtn").disabled = false;

    document.getElementById("loginBtn").textContent = "Connect to LeadDesk";

  }

}


async function fetchAll() {

  try {

    const [callsRes, agentsRes] = await Promise.allSettled([

      apiFetch("/calls?per_page=100"),

      apiFetch("/users?per_page=100")

    ]);

    const calls = callsRes.status === "fulfilled" ? (callsRes.value?.collection || callsRes.value?.data || []) : [];

    const agents = agentsRes.status === "fulfilled" ? (agentsRes.value?.collection || agentsRes.value?.data || []) : [];


    const totalCalls = calls.length;

    const answered = calls.filter(c => c.status?.toLowerCase() === "answered" || c.answered).length;

    const missed = calls.filter(c => c.status?.toLowerCase() === "missed" || c.missed).length;

    const avgDuration = totalCalls > 0 ? Math.round(calls.reduce((a, c) => a + (c.duration || 0), 0) / totalCalls) : 0;

    const answerRate = totalCalls > 0 ? (answered / totalCalls) * 100 : 0;


    const byStatus = {};

    calls.forEach(c => { const s = c.status || "unknown"; byStatus[s] = (byStatus[s] || 0) + 1; });


    const byHour = Array.from({ length: 24 }, (_, h) => ({

      label: h % 4 === 0 ? `${h}:00` : "",

      value: calls.filter(c => new Date(c.created_at || c.start_time || 0).getHours() === h).length

    }));


    const byAgent = {};

    calls.forEach(c => {

      const id = c.user_id || c.agent_id || "unknown";

      if (!byAgent[id]) byAgent[id] = { answered: 0, total: 0, duration: 0 };

      byAgent[id].total++;

      if (c.status?.toLowerCase() === "answered" || c.answered) byAgent[id].answered++;

      byAgent[id].duration += c.duration || 0;

    });


    const agentMap = {};

    agents.forEach(a => { agentMap[a.id] = a.username || a.name || `Agent ${a.id}`; });


    const agentStats = Object.entries(byAgent).map(([id, s]) => ({

      name: agentMap[id] || `Agent ${id}`,

      total: s.total, answered: s.answered,

      rate: s.total > 0 ? ((s.answered / s.total) * 100).toFixed(1) : "0",

      avgDur: s.total > 0 ? Math.round(s.duration / s.total) : 0

    })).sort((a, b) => b.total - a.total).slice(0, 10);


    dashData = { calls, agents, totalCalls, answered, missed, avgDuration, answerRate, byStatus, byHour, agentStats, totalAgents: agents.length };

    renderDashboard();

  } catch(e) {

    renderError(e.message);

  }

}


function renderLogin() {

  document.getElementById("root").innerHTML = `

    <div class="login-wrap">

      <div class="login-card">

        <h1>LeadDesk Dashboard</h1>

        <p>Enter your API credentials to connect</p>

        <div class="field"><label>API key (client ID)</label><input id="clientId" type="text" placeholder="your-api-key"></div>

        <div class="field"><label>Client secret</label><input id="clientSecret" type="password" placeholder="your-client-secret"></div>

        <div class="field"><label>LeadDesk customer ID</label><input id="customerId" type="text" placeholder="e.g. 12345"></div>

        <div id="loginError" class="error-msg"></div>

        <button id="loginBtn" class="btn-primary" style="width:100%;margin-top:4px" onclick="doLogin()">Connect to LeadDesk</button>

        <p class="hint">Credentials are used only to fetch an OAuth token and are never stored.</p>

      </div>

    </div>`;

}


function renderLoading() {

  document.getElementById("root").innerHTML = `<div class="loading">Connecting to LeadDesk…</div>`;

}


function renderError(msg) {

  document.getElementById("root").innerHTML = `

    <div class="login-wrap">

      <div class="login-card">

        <p class="error-msg">${msg}</p>

        <button class="btn-primary" onclick="renderLogin()">Back to login</button>

      </div>

    </div>`;

}


function setTab(t) { currentTab = t; renderDashboard(); }


function renderDashboard() {

  const { totalCalls, answered, missed, avgDuration, answerRate, byStatus, byHour, agentStats, calls, totalAgents } = dashData;

  const maxStatus = Math.max(...Object.values(byStatus || {}), 1);

  const maxBar = Math.max(...(byHour || []).map(d => d.value), 1);


  const statusRows = Object.entries(byStatus || {}).sort((a,b) => b[1]-a[1]).map(([s, v]) => `

    <div class="mini-bar-row">

      <div class="mini-bar-top"><span style="text-transform:capitalize">${s}</span><span style="color:#888780">${v}</span></div>

      <div class="mini-bar-track"><div class="mini-bar-fill" style="width:${Math.round((v/maxStatus)*100)}%"></div></div>

    </div>`).join("") || `<p style="font-size:12px;color:#b4b2a9">No data</p>`;


  const barCols = (byHour || []).map(d => `

    <div class="bar-col">

      <div class="bar" style="height:${Math.round((d.value/maxBar)*68)||2}px"></div>

      <span class="bar-lbl">${d.label}</span>

    </div>`).join("");


  const agentRows = agentStats?.length > 0 ? agentStats.map(a => `

    <tr>

      <td>${a.name}</td><td>${a.total}</td><td>${a.answered}</td><td>${a.rate}%</td><td>${dur(a.avgDur)}</td>

    </tr>`).join("") : `<tr><td colspan="5" style="text-align:center;color:#b4b2a9;padding:1.5rem">No agent data</td></tr>`;


  const recentRows = calls?.slice(0, 25).map(c => {

    const d = c.created_at || c.start_time ? new Date(c.created_at || c.start_time) : null;

    return `<tr>

      <td style="color:#888780">${d ? d.toLocaleTimeString([], {hour:"2-digit",minute:"2-digit"}) : "—"}</td>

      <td style="text-transform:capitalize">${c.direction || c.type || "—"}</td>

      <td><span class="${badgeClass(c.status)}">${c.status || "unknown"}</span></td>

      <td>${dur(c.duration)}</td>

      <td style="font-family:monospace;color:#888780">${c.phone_number || c.callee || c.caller || "—"}</td>

    </tr>`;

  }).join("") || `<tr><td colspan="5" style="text-align:center;color:#b4b2a9;padding:1.5rem">No call records</td></tr>`;


  const tabs = ["overview","agents","recent"].map(t => `

    <button onclick="setTab('${t}')" style="background:none;border:none;border-bottom:${currentTab===t?"2px solid #1a1a18":"2px solid transparent"};border-radius:0;padding:6px 4px;font-size:13px;font-weight:${currentTab===t?"500":"400"};color:${currentTab===t?"#1a1a18":"#888780"};cursor:pointer;margin-right:12px">

      ${t.charAt(0).toUpperCase()+t.slice(1)}

    </button>`).join("");


  const overviewTab = `

    <div class="grid-2">

      <div class="card">

        <div class="card-title">Calls by status</div>

        ${statusRows}

      </div>

      <div class="card">

        <div class="card-title">Calls by hour</div>

        <div class="bar-chart">${barCols}</div>

      </div>

    </div>`;


  const agentsTab = `

    <div class="card" style="padding:0;overflow:hidden">

      <div class="tbl-wrap">

        <table>

          <thead><tr><th>Agent</th><th>Total</th><th>Answered</th><th>Answer rate</th><th>Avg duration</th></tr></thead>

          <tbody>${agentRows}</tbody>

        </table>

      </div>

    </div>`;


  const recentTab = `

    <div class="card" style="padding:0;overflow:hidden">

      <div class="tbl-wrap">

        <table>

          <thead><tr><th>Time</th><th>Direction</th><th>Status</th><th>Duration</th><th>Number</th></tr></thead>

          <tbody>${recentRows}</tbody>

        </table>

      </div>

    </div>`;


  document.getElementById("root").innerHTML = `

    <div class="app">

      <div class="sidebar">

        <h2>LeadDesk</h2>

        <p>Calls & activity</p>

        <button class="nav-item active">Overview</button>

        <div style="margin-top:auto;padding-top:2rem">

          <button class="disconnect" onclick="token=null;renderLogin()">Disconnect</button>

        </div>

      </div>

      <div class="main">

        <div class="page-title">Calls & activity</div>

        <div class="page-sub">Live data · LeadDesk CEU</div>

        <div class="metrics">

          <div class="metric"><div class="metric-label">Total calls</div><div class="metric-value">${fmt(totalCalls)}</div></div>

          <div class="metric"><div class="metric-label">Answered</div><div class="metric-value">${fmt(answered)}</div><div class="metric-sub">${pct(answerRate)} answer rate</div></div>

          <div class="metric"><div class="metric-label">Missed</div><div class="metric-value">${fmt(missed)}</div></div>

          <div class="metric"><div class="metric-label">Avg duration</div><div class="metric-value">${dur(avgDuration)}</div></div>

          <div class="metric"><div class="metric-label">Agents</div><div class="metric-value">${fmt(totalAgents)}</div></div>

        </div>

        <div style="display:flex;gap:8px;margin-bottom:1.25rem;border-bottom:1px solid #d3d1c7;padding-bottom:0">${tabs}</div>

        ${currentTab === "overview" ? overviewTab : currentTab === "agents" ? agentsTab : recentTab}

      </div>

    </div>`;

}


renderLogin();

</script>

</body>

</html>