403Webshell
Server IP : 69.72.244.102  /  Your IP : 216.73.216.164
Web Server : LiteSpeed
System : Linux s3434.fra1.stableserver.net 4.18.0-513.24.1.lve.2.el8.x86_64 #1 SMP Fri May 24 12:42:50 UTC 2024 x86_64
User : konzalta ( 1271)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/konzalta/constituency.work/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/konzalta/constituency.work/complaints.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ConstituencyOS – Complaints</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
  --green:#1a7a4a; --green-light:#e8f5ee; --green-mid:#2d9e63;
  --red:#dc2626; --red-light:#fdecea;
  --amber:#f59e0b; --amber-light:#fffbeb;
  --blue:#2563eb; --blue-light:#eff6ff;
  --purple:#7c3aed; --purple-light:#f5f3ff;
  --text:#111827; --muted:#6b7280; --light:#9ca3af;
  --border:#e5e7eb; --bg:#f3f4f6; --white:#ffffff;
  --sidebar-w:216px; --radius:10px; --radius-sm:6px;
  --shadow:0 1px 3px rgba(0,0,0,.08);
}
html,body{height:100%;margin:0;padding:0;}
body{font-family:'Inter',sans-serif;font-size:13px;color:var(--text);background:var(--bg);}

/* SIDEBAR */
.sidebar{position:fixed;top:0;left:0;bottom:0;width:var(--sidebar-w);background:var(--white);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow-y:auto;overflow-x:hidden;z-index:40;}
.sidebar-logo{display:flex;align-items:center;gap:9px;padding:14px 14px 12px;border-bottom:1px solid var(--border);flex-shrink:0;}
.logo-icon{width:38px;height:38px;background:linear-gradient(135deg,#b91c1c,#1a7a4a);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:17px;flex-shrink:0;}
.logo-text strong{display:block;font-size:12.5px;font-weight:700;color:var(--green);}
.logo-text small{font-size:10px;color:var(--muted);line-height:1.3;}
.sidebar-profile{display:flex;align-items:center;gap:9px;padding:11px 14px;border-bottom:1px solid var(--border);flex-shrink:0;}
.avatar{width:36px;height:36px;border-radius:50%;background:#374151;color:white;font-weight:600;font-size:13px;display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;}
.avatar .dot{position:absolute;bottom:1px;right:1px;width:8px;height:8px;border-radius:50%;background:var(--green-mid);border:2px solid white;}
.profile-name{font-size:11.5px;font-weight:600;}.profile-sub{font-size:10.5px;color:var(--muted);}
.sidebar-nav{flex:1;padding:6px 0;}
.nav-item{display:flex;align-items:center;gap:9px;padding:8px 14px;font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;position:relative;transition:background .12s,color .12s;white-space:nowrap;}
.nav-item:hover{background:var(--bg);color:var(--text);}
.nav-item.active{background:var(--green-light);color:var(--green);font-weight:600;}
.nav-item.active::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--green);border-radius:0 2px 2px 0;}
.nav-icon{font-size:14px;width:16px;text-align:center;flex-shrink:0;}
.nav-badge{margin-left:auto;background:var(--red);color:white;font-size:9.5px;font-weight:700;padding:1px 5px;border-radius:10px;}
.nav-new{margin-left:auto;background:var(--green);color:white;font-size:9px;font-weight:700;padding:2px 5px;border-radius:4px;}
.nav-chevron{margin-left:auto;font-size:10px;color:var(--light);}
.sidebar-footer{padding:12px 14px;border-top:1px solid var(--border);flex-shrink:0;}
.help-row{display:flex;align-items:center;gap:9px;cursor:pointer;}
.help-label{font-size:11.5px;font-weight:600;}.help-sub{font-size:10.5px;color:var(--muted);}

/* RIGHT PANEL */
.right-panel{margin-left:var(--sidebar-w);display:flex;flex-direction:column;min-height:100vh;}
.topbar{position:sticky;top:0;z-index:30;height:52px;background:var(--white);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;padding:0 18px;}
.menu-btn{font-size:17px;cursor:pointer;color:var(--muted);}
.const-selector{display:flex;align-items:center;gap:6px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:5px 10px;cursor:pointer;font-size:12px;font-weight:500;}
.search-top{flex:1;max-width:360px;display:flex;align-items:center;gap:7px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 11px;color:var(--muted);font-size:12px;}
.search-top kbd{margin-left:auto;font-size:9.5px;background:var(--border);padding:1px 5px;border-radius:3px;font-family:inherit;}
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:8px;}
.icon-btn{position:relative;width:32px;height:32px;border-radius:50%;background:var(--bg);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:15px;}
.icon-btn .bdg{position:absolute;top:-3px;right:-3px;background:var(--red);color:white;font-size:9px;font-weight:700;width:15px;height:15px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:1.5px solid white;}
.icon-btn .bdg.grn{background:var(--green-mid);}
.user-pill{display:flex;align-items:center;gap:7px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:3px 8px;cursor:pointer;font-size:12px;font-weight:500;}

/* CONTENT */
.content{padding:18px 18px 40px;display:flex;flex-direction:column;gap:14px;}

/* PAGE HEADER */
.page-header{display:flex;align-items:center;justify-content:space-between;}
.page-title{font-size:19px;font-weight:700;}
.page-sub{font-size:12px;color:var(--muted);margin-top:2px;}
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;border:none;transition:background .15s;}
.btn-primary{background:var(--green);color:white;}.btn-primary:hover{background:var(--green-mid);}
.btn-ghost{background:var(--white);color:var(--text);border:1px solid var(--border);}.btn-ghost:hover{background:var(--bg);}

/* STAT STRIP */
.stat-strip{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;}
.sstat{background:var(--white);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;cursor:pointer;transition:box-shadow .15s;}
.sstat:hover{box-shadow:0 4px 12px rgba(0,0,0,.09);}
.sstat.active-filter{border-color:var(--green);box-shadow:0 0 0 2px var(--green-light);}
.sstat-val{font-size:24px;font-weight:700;line-height:1.1;}
.sstat-lbl{font-size:11px;color:var(--muted);margin-top:2px;}
.sstat-bar{height:3px;border-radius:3px;margin-top:8px;}

/* TOOLBAR */
.toolbar{display:flex;align-items:center;gap:10px;background:var(--white);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;}
.search-input{flex:1;display:flex;align-items:center;gap:7px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;font-size:12px;}
.search-input input{border:none;background:transparent;outline:none;width:100%;font-size:12px;font-family:inherit;color:var(--text);}
.filter-select{display:flex;align-items:center;gap:5px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;font-size:12px;cursor:pointer;color:var(--text);white-space:nowrap;}
.filter-select select{border:none;background:transparent;outline:none;font-size:12px;font-family:inherit;color:var(--text);cursor:pointer;}
.view-toggle{display:flex;gap:2px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:2px;}
.vt-btn{width:28px;height:26px;border-radius:4px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:13px;color:var(--muted);}
.vt-btn.active{background:var(--white);color:var(--text);box-shadow:var(--shadow);}

/* TABLE */
.table-card{background:var(--white);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;}
.table-wrap{overflow-x:auto;}
table{width:100%;border-collapse:collapse;}
thead tr{background:var(--bg);border-bottom:1px solid var(--border);}
th{padding:9px 14px;font-size:11px;font-weight:600;color:var(--muted);text-align:left;white-space:nowrap;cursor:pointer;user-select:none;}
th:hover{color:var(--text);}
th .sort-icon{margin-left:3px;color:var(--light);}
tbody tr{border-bottom:1px solid var(--border);transition:background .1s;cursor:pointer;}
tbody tr:last-child{border-bottom:none;}
tbody tr:hover{background:#f9fafb;}
td{padding:10px 14px;font-size:12px;vertical-align:middle;}
.td-id{font-size:11px;color:var(--muted);font-weight:600;}
.td-title{font-weight:600;font-size:12.5px;}
.td-sub{font-size:10.5px;color:var(--muted);margin-top:1px;}
.td-ward{font-size:11.5px;}
.td-date{font-size:11px;color:var(--muted);white-space:nowrap;}
.ptag{display:inline-flex;align-items:center;font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;white-space:nowrap;}
.ptag.hi{background:#fdecea;color:#b91c1c;}
.ptag.md{background:#fff7ed;color:#c2410c;}
.ptag.lo{background:#ecfdf5;color:#065f46;}
.stag{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:3px 8px;border-radius:20px;white-space:nowrap;}
.stag.open{background:#eff6ff;color:#1d4ed8;}
.stag.progress{background:#fff7ed;color:#b45309;}
.stag.resolved{background:#ecfdf5;color:#065f46;}
.stag.escalated{background:#fdf4ff;color:#7e22ce;}
.action-btns{display:flex;gap:5px;opacity:0;transition:opacity .15s;}
tbody tr:hover .action-btns{opacity:1;}
.ab{width:26px;height:26px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--white);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;}
.ab:hover{background:var(--bg);}

/* PAGINATION */
.pagination{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-top:1px solid var(--border);}
.pg-info{font-size:11.5px;color:var(--muted);}
.pg-btns{display:flex;gap:4px;}
.pg-btn{width:28px;height:28px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--white);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;font-weight:500;}
.pg-btn.active{background:var(--green);color:white;border-color:var(--green);}
.pg-btn:hover:not(.active){background:var(--bg);}

/* DETAIL PANEL */
.detail-overlay{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:100;display:none;align-items:flex-start;justify-content:flex-end;}
.detail-overlay.open{display:flex;}
.detail-panel{width:420px;height:100%;background:var(--white);box-shadow:-4px 0 24px rgba(0,0,0,.12);display:flex;flex-direction:column;overflow:hidden;}
.dp-header{display:flex;align-items:center;justify-content:space-between;padding:16px 18px;border-bottom:1px solid var(--border);flex-shrink:0;}
.dp-title{font-size:15px;font-weight:700;}
.dp-close{width:28px;height:28px;border-radius:50%;background:var(--bg);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;}
.dp-body{flex:1;overflow-y:auto;padding:18px;}
.dp-section{margin-bottom:18px;}
.dp-section-title{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;}
.dp-row{display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border);font-size:12px;}
.dp-row:last-child{border-bottom:none;}
.dp-key{color:var(--muted);}.dp-val{font-weight:500;text-align:right;}
.dp-desc{font-size:12px;line-height:1.6;color:var(--text);background:var(--bg);border-radius:var(--radius-sm);padding:10px 12px;}
.timeline{display:flex;flex-direction:column;gap:0;}
.tl-item{display:flex;gap:10px;padding-bottom:14px;position:relative;}
.tl-item:last-child{padding-bottom:0;}
.tl-item:not(:last-child)::after{content:'';position:absolute;left:11px;top:22px;bottom:0;width:1px;background:var(--border);}
.tl-dot{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;flex-shrink:0;border:2px solid var(--white);box-shadow:0 0 0 1px var(--border);}
.tl-content{flex:1;padding-top:2px;}
.tl-action{font-size:12px;font-weight:600;}
.tl-meta{font-size:10.5px;color:var(--muted);margin-top:1px;}
.dp-footer{padding:14px 18px;border-top:1px solid var(--border);display:flex;gap:8px;flex-shrink:0;}
.dp-footer .btn{flex:1;justify-content:center;}
textarea.dp-note{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 10px;font-size:12px;font-family:inherit;resize:none;height:64px;outline:none;color:var(--text);}
textarea.dp-note:focus{border-color:var(--green);}
</style>
</head>
<body>

<!-- SIDEBAR -->
<aside class="sidebar">
  <div class="sidebar-logo">
    <div class="logo-icon">πŸ›οΈ</div>
    <div class="logo-text"><strong>ConstituencyOS</strong><small>Everything in your constituency,<br>in one place.</small></div>
  </div>
  <div class="sidebar-profile">
    <div class="avatar">DM<div class="dot"></div></div>
    <div><div class="profile-name">Hon. David Mwangi, MP</div><div class="profile-sub">Embakasi South</div></div>
  </div>
  <nav class="sidebar-nav">
    <div class="nav-item" onclick="location.href='index.html'"><span class="nav-icon">🏠</span>Dashboard</div>
    <div class="nav-item" onclick="location.href='constituents.html'"><span class="nav-icon">πŸ‘₯</span>Constituents</div>
    <div class="nav-item" onclick="location.href='complaints.html'"><span class="nav-icon">πŸ“‹</span>Complaints<span class="nav-badge">9</span></div>
    <div class="nav-item" onclick="location.href='projects.html'"><span class="nav-icon">πŸ“</span>Projects</div>
    <div class="nav-item" onclick="location.href='bursaries.html'"><span class="nav-icon">πŸŽ“</span>Bursaries</div>
    <div class="nav-item" onclick="location.href='events.html'"><span class="nav-icon">πŸ“…</span>Events</div>
    <div class="nav-item" onclick="location.href='social_protection.html'"><span class="nav-icon">πŸ›‘οΈ</span>Social Protection</div>
    <div class="nav-item" onclick="location.href='ward_management.html'"><span class="nav-icon">🏘️</span>Ward Management</div>
    <div class="nav-item" onclick="location.href='finance.html'"><span class="nav-icon">πŸ’°</span>Finance</div>
    <div class="nav-item" onclick="location.href='documents.html'"><span class="nav-icon">πŸ“„</span>Documents</div>
    <div class="nav-item" onclick="location.href='staff_management.html'"><span class="nav-icon">πŸ‘€</span>Staff Management</div>
    <div class="nav-item" onclick="location.href='reports_analytics.html'"><span class="nav-icon">πŸ“Š</span>Reports & Analytics</div>
    <div class="nav-item active"><span class="nav-icon">πŸ€–</span>AI Assistant<span class="nav-new">New</span></div>
    <div class="nav-item" onclick="location.href='settings.html'"><span class="nav-icon">βš™οΈ</span>Settings</div>
    <div class="nav-item" onclick="location.href='public_portal.html'"><span class="nav-icon">🌐</span>Public Portal</div>
  </nav>
  <div class="sidebar-footer">
    <div class="help-row"><span>❓</span><div><div class="help-label">Need Help?</div><div class="help-sub">Contact Support</div></div></div>
  </div>
</aside>

<!-- RIGHT PANEL -->
<div class="right-panel">
  <header class="topbar">
    <span class="menu-btn">☰</span>
    <div class="const-selector"><span style="font-size:13px">πŸ›οΈ</span>Embakasi South Constituency<span style="font-size:10px;color:var(--muted)">β–Ό</span></div>
    <div class="search-top"><span>πŸ”</span><input type="text" placeholder="Search for anything..." style="border:none;background:transparent;outline:none;width:100%;font-size:12px;font-family:inherit;"><kbd>⌘K</kbd></div>
    <div class="topbar-right">
      <div class="icon-btn">πŸ””<div class="bdg">8</div></div>
      <div class="icon-btn">βœ‰οΈ<div class="bdg grn">3</div></div>
      <div class="user-pill"><div class="avatar" style="width:26px;height:26px;font-size:10px">DM</div>Hon. David Mwangi<span style="font-size:10px;color:var(--muted)">β–Ό</span></div>
    </div>
  </header>

  <main class="content">

    <!-- Page Header -->
    <div class="page-header">
      <div>
        <div class="page-title">Complaints</div>
        <div class="page-sub">Manage and resolve constituent complaints across all wards</div>
      </div>
      <div style="display:flex;gap:8px;">
        <button class="btn btn-ghost" onclick="exportCSV()">⬇ Export</button>
        <button class="btn btn-primary" onclick="openNew()">οΌ‹ New Complaint</button>
      </div>
    </div>

    <!-- Stat Strip -->
    <div class="stat-strip">
      <div class="sstat active-filter" onclick="filterStatus('all',this)">
        <div class="sstat-val" id="s-all">23</div>
        <div class="sstat-lbl">All Complaints</div>
        <div class="sstat-bar" style="background:var(--border)"></div>
      </div>
      <div class="sstat" onclick="filterStatus('Open',this)">
        <div class="sstat-val" style="color:var(--blue)" id="s-open">9</div>
        <div class="sstat-lbl">Open</div>
        <div class="sstat-bar" style="background:var(--blue);opacity:.3"></div>
      </div>
      <div class="sstat" onclick="filterStatus('In Progress',this)">
        <div class="sstat-val" style="color:var(--amber)" id="s-progress">8</div>
        <div class="sstat-lbl">In Progress</div>
        <div class="sstat-bar" style="background:var(--amber);opacity:.3"></div>
      </div>
      <div class="sstat" onclick="filterStatus('Resolved',this)">
        <div class="sstat-val" style="color:var(--green)" id="s-resolved">4</div>
        <div class="sstat-lbl">Resolved</div>
        <div class="sstat-bar" style="background:var(--green);opacity:.3"></div>
      </div>
      <div class="sstat" onclick="filterStatus('Escalated',this)">
        <div class="sstat-val" style="color:var(--purple)" id="s-escalated">2</div>
        <div class="sstat-lbl">Escalated</div>
        <div class="sstat-bar" style="background:var(--purple);opacity:.3"></div>
      </div>
    </div>

    <!-- Toolbar -->
    <div class="toolbar">
      <div class="search-input">
        <span>πŸ”</span>
        <input type="text" id="searchInput" placeholder="Search complaints…" oninput="applyFilters()">
      </div>
      <div class="filter-select">🏘️ <select id="wardFilter" onchange="applyFilters()">
        <option value="">All Wards</option>
        <option>Pipeline Ward</option>
        <option>Imara Daima</option>
        <option>Mabatini Ward</option>
        <option>Umoja Ward</option>
        <option>Kwa Njenga Ward</option>
        <option>Mihango Ward</option>
      </select></div>
      <div class="filter-select">⚑ <select id="priorityFilter" onchange="applyFilters()">
        <option value="">All Priorities</option>
        <option>High</option>
        <option>Medium</option>
        <option>Low</option>
      </select></div>
      <div class="filter-select">πŸ“‚ <select id="catFilter" onchange="applyFilters()">
        <option value="">All Categories</option>
        <option>Water</option>
        <option>Roads</option>
        <option>Electricity</option>
        <option>Sanitation</option>
        <option>Security</option>
        <option>Health</option>
      </select></div>
      <div class="view-toggle">
        <div class="vt-btn active" onclick="setView('table',this)" title="Table">☰</div>
        <div class="vt-btn" onclick="setView('cards',this)" title="Cards">⊞</div>
      </div>
    </div>

    <!-- Table -->
    <div class="table-card">
      <div class="table-wrap">
        <table id="complaintsTable">
          <thead>
            <tr>
              <th onclick="sortBy('id')">ID <span class="sort-icon">↕</span></th>
              <th onclick="sortBy('title')">Complaint <span class="sort-icon">↕</span></th>
              <th onclick="sortBy('ward')">Ward <span class="sort-icon">↕</span></th>
              <th onclick="sortBy('category')">Category <span class="sort-icon">↕</span></th>
              <th onclick="sortBy('priority')">Priority <span class="sort-icon">↕</span></th>
              <th onclick="sortBy('status')">Status <span class="sort-icon">↕</span></th>
              <th onclick="sortBy('date')">Date <span class="sort-icon">↕</span></th>
              <th onclick="sortBy('assignee')">Assigned To <span class="sort-icon">↕</span></th>
              <th></th>
            </tr>
          </thead>
          <tbody id="tableBody"></tbody>
        </table>
      </div>
      <div class="pagination">
        <div class="pg-info" id="pgInfo"></div>
        <div class="pg-btns" id="pgBtns"></div>
      </div>
    </div>

  </main>
</div>

<!-- DETAIL PANEL -->
<div class="detail-overlay" id="detailOverlay" onclick="closeDetail(event)">
  <div class="detail-panel" id="detailPanel">
    <div class="dp-header">
      <div>
        <div class="td-id" id="dp-id"></div>
        <div class="dp-title" id="dp-title"></div>
      </div>
      <div class="dp-close" onclick="closeDetailDirect()">βœ•</div>
    </div>
    <div class="dp-body">
      <div style="display:flex;gap:8px;margin-bottom:16px;" id="dp-tags"></div>

      <div class="dp-section">
        <div class="dp-section-title">Details</div>
        <div class="dp-row"><span class="dp-key">Ward</span><span class="dp-val" id="dp-ward"></span></div>
        <div class="dp-row"><span class="dp-key">Category</span><span class="dp-val" id="dp-cat"></span></div>
        <div class="dp-row"><span class="dp-key">Submitted by</span><span class="dp-val" id="dp-by"></span></div>
        <div class="dp-row"><span class="dp-key">Date Submitted</span><span class="dp-val" id="dp-date"></span></div>
        <div class="dp-row"><span class="dp-key">Assigned To</span><span class="dp-val" id="dp-assigned"></span></div>
      </div>

      <div class="dp-section">
        <div class="dp-section-title">Description</div>
        <div class="dp-desc" id="dp-desc"></div>
      </div>

      <div class="dp-section">
        <div class="dp-section-title">Activity Timeline</div>
        <div class="timeline" id="dp-timeline"></div>
      </div>

      <div class="dp-section">
        <div class="dp-section-title">Add Note</div>
        <textarea class="dp-note" id="dp-note-input" placeholder="Add a note or update…"></textarea>
        <button class="btn btn-ghost" style="margin-top:6px;font-size:11.5px;" onclick="addNote()">Post Note</button>
      </div>
    </div>
    <div class="dp-footer">
      <button class="btn btn-ghost" id="dp-escalate-btn" onclick="escalateComplaint()">πŸ”Ί Escalate</button>
      <button class="btn btn-primary" id="dp-resolve-btn" onclick="resolveComplaint()">βœ“ Mark Resolved</button>
    </div>
  </div>
</div>

<script>
const COMPLAINTS = [
  {id:'#1428',title:'Water pipe burst',ward:'Pipeline Ward',category:'Water',priority:'Medium',status:'Open',date:'20 May 2024',assignee:'Eng. Kamau',by:'Jane Wanjiku',desc:'A major water pipe has burst along Kiandutu Road, flooding the road and cutting off supply to over 200 households. Residents have been without water since 6 AM.',timeline:[{action:'Complaint submitted',by:'Jane Wanjiku',time:'Today 8:30 AM',color:'#2563eb'},{action:'Assigned to Eng. Kamau',by:'System',time:'Today 8:45 AM',color:'#6b7280'}]},
  {id:'#1427',title:'Broken bridge',ward:'Imara Daima',category:'Roads',priority:'High',status:'Escalated',date:'19 May 2024',assignee:'Roads Dept',by:'Peter Ochieng',desc:'The footbridge connecting Kwa Reuben estate to the main road has partially collapsed. Several people use it daily for school and market access. Risk of injury is high.',timeline:[{action:'Complaint submitted',by:'Peter Ochieng',time:'Yesterday 4:15 PM',color:'#2563eb'},{action:'Assessed by ward engineer',by:'Eng. Mwangi',time:'Yesterday 6:00 PM',color:'#f59e0b'},{action:'Escalated to Roads Dept',by:'Admin',time:'Today 9:00 AM',color:'#7c3aed'}]},
  {id:'#1426',title:'Streetlight not working',ward:'Mabatini Ward',category:'Electricity',priority:'Low',status:'Resolved',date:'19 May 2024',assignee:'KPLC Liaison',by:'Mary Akinyi',desc:'Three streetlights along Soweto Road have been off for two weeks. The area becomes very dark at night causing safety concerns for pedestrians.',timeline:[{action:'Complaint submitted',by:'Mary Akinyi',time:'Yesterday 11:20 AM',color:'#2563eb'},{action:'KPLC notified',by:'Admin',time:'Yesterday 2:00 PM',color:'#f59e0b'},{action:'Lights repaired & tested',by:'KPLC Liaison',time:'Today 7:00 AM',color:'#16a34a'},{action:'Marked resolved',by:'Admin',time:'Today 7:30 AM',color:'#16a34a'}]},
  {id:'#1425',title:'Garbage collection delay',ward:'Umoja Ward',category:'Sanitation',priority:'Medium',status:'In Progress',date:'18 May 2024',assignee:'Sanitation Team',by:'David Otieno',desc:'Garbage has not been collected in Umoja II for 10 days. The waste is piling up near the market and posing a health hazard to nearby residents and traders.',timeline:[{action:'Complaint submitted',by:'David Otieno',time:'18 May 2024',color:'#2563eb'},{action:'Sanitation team assigned',by:'Admin',time:'18 May 2024',color:'#f59e0b'},{action:'Partial collection done',by:'Sanitation Team',time:'19 May 2024',color:'#f59e0b'}]},
  {id:'#1424',title:'Drainage blockage',ward:'Kwa Njenga Ward',category:'Sanitation',priority:'High',status:'Open',date:'17 May 2024',assignee:'Unassigned',by:'Grace Muthoni',desc:'The main drainage channel near Kware market is fully blocked causing flooding whenever it rains. Several homes have been affected with water entering houses.',timeline:[{action:'Complaint submitted',by:'Grace Muthoni',time:'17 May 2024',color:'#2563eb'}]},
  {id:'#1423',title:'Road pothole – Mihango Rd',ward:'Mihango Ward',category:'Roads',priority:'High',status:'In Progress',date:'17 May 2024',assignee:'Eng. Kamau',by:'Samuel Njoroge',desc:'A large pothole on Mihango Road near the dispensary has caused three motorcycle accidents this week. The road needs urgent patching.',timeline:[{action:'Complaint submitted',by:'Samuel Njoroge',time:'17 May 2024',color:'#2563eb'},{action:'Site inspection done',by:'Eng. Kamau',time:'18 May 2024',color:'#f59e0b'}]},
  {id:'#1422',title:'School fence vandalized',ward:'Pipeline Ward',category:'Security',priority:'Medium',status:'Open',date:'16 May 2024',assignee:'Unassigned',by:'Ann Chebet',desc:'The perimeter fence of Pipeline Primary School has been vandalized and sections removed. Students are at risk and items have been stolen from the compound.',timeline:[{action:'Complaint submitted',by:'Ann Chebet',time:'16 May 2024',color:'#2563eb'}]},
  {id:'#1421',title:'Health clinic out of stock',ward:'Imara Daima',category:'Health',priority:'High',status:'Resolved',date:'16 May 2024',assignee:'Health Dept',by:'Paul Mutua',desc:'Imara Daima Clinic has run out of malaria medication and bandages. Patients are being turned away. Urgent restocking needed.',timeline:[{action:'Complaint submitted',by:'Paul Mutua',time:'16 May 2024',color:'#2563eb'},{action:'Health Dept notified',by:'Admin',time:'16 May 2024',color:'#f59e0b'},{action:'Supplies delivered',by:'Health Dept',time:'17 May 2024',color:'#16a34a'},{action:'Resolved',by:'Admin',time:'17 May 2024',color:'#16a34a'}]},
  {id:'#1420',title:'Borehole pump failure',ward:'Mabatini Ward',category:'Water',priority:'High',status:'In Progress',date:'15 May 2024',assignee:'Water Dept',by:'Rose Kamau',desc:'The community borehole pump serving Mabatini has broken down. Over 500 households depend on this water source and have had no water for 5 days.',timeline:[{action:'Complaint submitted',by:'Rose Kamau',time:'15 May 2024',color:'#2563eb'},{action:'Technician dispatched',by:'Water Dept',time:'15 May 2024',color:'#f59e0b'},{action:'Parts ordered',by:'Water Dept',time:'16 May 2024',color:'#f59e0b'}]},
  {id:'#1419',title:'Illegal dumping – river bank',ward:'Kwa Njenga Ward',category:'Sanitation',priority:'Medium',status:'Open',date:'15 May 2024',assignee:'Unassigned',by:'Kevin Omondi',desc:'A group has been dumping construction waste and garbage on the river bank near Kware, blocking water flow and polluting the river used by downstream communities.',timeline:[{action:'Complaint submitted',by:'Kevin Omondi',time:'15 May 2024',color:'#2563eb'}]},
  {id:'#1418',title:'Market stall conflict',ward:'Mihango Ward',category:'Security',priority:'Low',status:'Open',date:'14 May 2024',assignee:'Ward Officer',by:'Fatuma Hassan',desc:'Two traders at Mihango market are disputing ownership of stalls 14 and 15. The conflict has become physical on two occasions and market operations are disrupted.',timeline:[{action:'Complaint submitted',by:'Fatuma Hassan',time:'14 May 2024',color:'#2563eb'},{action:'Referred to Ward Officer',by:'Admin',time:'14 May 2024',color:'#6b7280'}]},
  {id:'#1417',title:'Transformer overloading',ward:'Umoja Ward',category:'Electricity',priority:'High',status:'Escalated',date:'14 May 2024',assignee:'KPLC Liaison',by:'James Mburu',desc:'The transformer serving Umoja II zone 4 is overloaded causing frequent blackouts and damage to appliances. Residents have raised this with KPLC but no action taken.',timeline:[{action:'Complaint submitted',by:'James Mburu',time:'14 May 2024',color:'#2563eb'},{action:'Escalated to KPLC',by:'Admin',time:'15 May 2024',color:'#7c3aed'}]},
  {id:'#1416',title:'Primary school leaking roof',ward:'Pipeline Ward',category:'Health',priority:'Medium',status:'In Progress',date:'13 May 2024',assignee:'Public Works',by:'Nancy Wangari',desc:'Three classrooms at Kiandutu Primary School have leaking roofs. When it rains, classes are disrupted and children get wet. The school has reported this for months.',timeline:[{action:'Complaint submitted',by:'Nancy Wangari',time:'13 May 2024',color:'#2563eb'},{action:'Inspection scheduled',by:'Public Works',time:'14 May 2024',color:'#f59e0b'}]},
  {id:'#1415',title:'Noise pollution – quarry',ward:'Imara Daima',category:'Security',priority:'Low',status:'Open',date:'13 May 2024',assignee:'Unassigned',by:'Lucy Wambua',desc:'A stone quarry operating near Imara Daima residential areas produces excessive noise and dust from 5 AM daily, affecting residents health and school children.',timeline:[{action:'Complaint submitted',by:'Lucy Wambua',time:'13 May 2024',color:'#2563eb'}]},
  {id:'#1414',title:'Street hawkers blocking clinic',ward:'Mabatini Ward',category:'Security',priority:'Low',status:'Resolved',date:'12 May 2024',assignee:'Ward Officer',by:'John Kariuki',desc:'Street hawkers have set up stalls blocking the entrance to Mabatini Dispensary. Ambulances and patients in wheelchairs cannot access the facility easily.',timeline:[{action:'Complaint submitted',by:'John Kariuki',time:'12 May 2024',color:'#2563eb'},{action:'Ward Officer intervened',by:'Ward Officer',time:'12 May 2024',color:'#f59e0b'},{action:'Hawkers relocated',by:'Ward Officer',time:'13 May 2024',color:'#16a34a'},{action:'Resolved',by:'Admin',time:'13 May 2024',color:'#16a34a'}]},
];

let filtered = [...COMPLAINTS];
let currentPage = 1;
const PAGE_SIZE = 10;
let sortKey = 'date';
let sortDir = -1;
let activeStatus = 'all';
let currentComplaint = null;

const priorityOrder = {High:3,Medium:2,Low:1};

function priorityTag(p){
  const cls = p==='High'?'hi':p==='Medium'?'md':'lo';
  return `<span class="ptag ${cls}">${p}</span>`;
}
function statusTag(s){
  const cls = s==='Open'?'open':s==='In Progress'?'progress':s==='Resolved'?'resolved':'escalated';
  const dot = s==='Open'?'●':s==='In Progress'?'β—‘':s==='Resolved'?'βœ“':'β–²';
  return `<span class="stag ${cls}">${dot} ${s}</span>`;
}

function applyFilters(){
  const q = document.getElementById('searchInput').value.toLowerCase();
  const ward = document.getElementById('wardFilter').value;
  const pri = document.getElementById('priorityFilter').value;
  const cat = document.getElementById('catFilter').value;
  filtered = COMPLAINTS.filter(c=>{
    if(activeStatus!=='all' && c.status!==activeStatus) return false;
    if(ward && c.ward!==ward) return false;
    if(pri && c.priority!==pri) return false;
    if(cat && c.category!==cat) return false;
    if(q && !`${c.id} ${c.title} ${c.ward} ${c.assignee} ${c.by}`.toLowerCase().includes(q)) return false;
    return true;
  });
  currentPage = 1;
  render();
}

function filterStatus(status, el){
  activeStatus = status;
  document.querySelectorAll('.sstat').forEach(s=>s.classList.remove('active-filter'));
  el.classList.add('active-filter');
  applyFilters();
}

function sortBy(key){
  if(sortKey===key) sortDir*=-1; else {sortKey=key;sortDir=1;}
  filtered.sort((a,b)=>{
    let av=a[key],bv=b[key];
    if(key==='priority'){av=priorityOrder[a.priority];bv=priorityOrder[b.priority];}
    return av>bv?sortDir:av<bv?-sortDir:0;
  });
  render();
}

function render(){
  const tbody = document.getElementById('tableBody');
  const start=(currentPage-1)*PAGE_SIZE, end=start+PAGE_SIZE;
  const page = filtered.slice(start,end);
  tbody.innerHTML = page.map(c=>`
    <tr onclick="openDetail('${c.id}')">
      <td class="td-id">${c.id}</td>
      <td><div class="td-title">${c.title}</div><div class="td-sub">${c.by}</div></td>
      <td class="td-ward">${c.ward}</td>
      <td>${c.category}</td>
      <td>${priorityTag(c.priority)}</td>
      <td>${statusTag(c.status)}</td>
      <td class="td-date">${c.date}</td>
      <td style="font-size:11.5px">${c.assignee}</td>
      <td><div class="action-btns">
        <div class="ab" title="View" onclick="event.stopPropagation();openDetail('${c.id}')">πŸ‘</div>
        <div class="ab" title="Assign" onclick="event.stopPropagation()">πŸ‘€</div>
        <div class="ab" title="Resolve" onclick="event.stopPropagation();quickResolve('${c.id}')">βœ“</div>
      </div></td>
    </tr>
  `).join('');

  // Pagination
  const total = filtered.length;
  const pages = Math.ceil(total/PAGE_SIZE);
  document.getElementById('pgInfo').textContent = `Showing ${Math.min(start+1,total)}–${Math.min(end,total)} of ${total} complaints`;
  const pb = document.getElementById('pgBtns');
  pb.innerHTML='';
  const addBtn=(label,p,active)=>{
    const d=document.createElement('div');
    d.className='pg-btn'+(active?' active':'');
    d.textContent=label;
    d.onclick=()=>{currentPage=p;render();};
    pb.appendChild(d);
  };
  if(currentPage>1) addBtn('β€Ή',currentPage-1,false);
  for(let i=1;i<=pages;i++) addBtn(i,i,i===currentPage);
  if(currentPage<pages) addBtn('β€Ί',currentPage+1,false);

  // Update sidebar badge
  const open = COMPLAINTS.filter(c=>c.status==='Open').length;
  document.getElementById('sidebarBadge').textContent = open;
}

function openDetail(id){
  currentComplaint = COMPLAINTS.find(c=>c.id===id);
  const c = currentComplaint;
  document.getElementById('dp-id').textContent = c.id;
  document.getElementById('dp-title').textContent = c.title;
  document.getElementById('dp-ward').textContent = c.ward;
  document.getElementById('dp-cat').textContent = c.category;
  document.getElementById('dp-by').textContent = c.by;
  document.getElementById('dp-date').textContent = c.date;
  document.getElementById('dp-assigned').textContent = c.assignee;
  document.getElementById('dp-desc').textContent = c.desc;
  document.getElementById('dp-tags').innerHTML = priorityTag(c.priority)+' '+statusTag(c.status);
  document.getElementById('dp-timeline').innerHTML = c.timeline.map(t=>`
    <div class="tl-item">
      <div class="tl-dot" style="background:${t.color}20;color:${t.color}">●</div>
      <div class="tl-content">
        <div class="tl-action">${t.action}</div>
        <div class="tl-meta">${t.by} Β· ${t.time}</div>
      </div>
    </div>
  `).join('');
  const resolved = c.status==='Resolved';
  document.getElementById('dp-resolve-btn').textContent = resolved ? 'βœ“ Resolved' : 'βœ“ Mark Resolved';
  document.getElementById('dp-resolve-btn').disabled = resolved;
  document.getElementById('detailOverlay').classList.add('open');
}

function closeDetail(e){
  if(e.target===document.getElementById('detailOverlay')) closeDetailDirect();
}
function closeDetailDirect(){
  document.getElementById('detailOverlay').classList.remove('open');
}

function resolveComplaint(){
  if(!currentComplaint||currentComplaint.status==='Resolved') return;
  currentComplaint.status='Resolved';
  currentComplaint.timeline.push({action:'Marked as resolved',by:'Hon. David Mwangi',time:'Just now',color:'#16a34a'});
  updateCounts();
  applyFilters();
  openDetail(currentComplaint.id);
}

function escalateComplaint(){
  if(!currentComplaint) return;
  currentComplaint.status='Escalated';
  currentComplaint.timeline.push({action:'Escalated by MP',by:'Hon. David Mwangi',time:'Just now',color:'#7c3aed'});
  updateCounts();
  applyFilters();
  openDetail(currentComplaint.id);
}

function addNote(){
  if(!currentComplaint) return;
  const txt = document.getElementById('dp-note-input').value.trim();
  if(!txt) return;
  currentComplaint.timeline.push({action:`Note: ${txt}`,by:'Hon. David Mwangi',time:'Just now',color:'#6b7280'});
  document.getElementById('dp-note-input').value='';
  openDetail(currentComplaint.id);
}

function quickResolve(id){
  const c = COMPLAINTS.find(x=>x.id===id);
  if(c && c.status!=='Resolved'){
    c.status='Resolved';
    c.timeline.push({action:'Quick resolved',by:'Admin',time:'Just now',color:'#16a34a'});
    updateCounts(); applyFilters();
  }
}

function updateCounts(){
  document.getElementById('s-all').textContent = COMPLAINTS.length;
  document.getElementById('s-open').textContent = COMPLAINTS.filter(c=>c.status==='Open').length;
  document.getElementById('s-progress').textContent = COMPLAINTS.filter(c=>c.status==='In Progress').length;
  document.getElementById('s-resolved').textContent = COMPLAINTS.filter(c=>c.status==='Resolved').length;
  document.getElementById('s-escalated').textContent = COMPLAINTS.filter(c=>c.status==='Escalated').length;
}

function openNew(){
  alert('New Complaint form β€” would open a modal in the full build.');
}
function exportCSV(){
  const rows=[['ID','Title','Ward','Category','Priority','Status','Date','Assignee'],...filtered.map(c=>[c.id,c.title,c.ward,c.category,c.priority,c.status,c.date,c.assignee])];
  const csv=rows.map(r=>r.join(',')).join('\n');
  const a=document.createElement('a');a.href='data:text/csv;charset=utf-8,'+encodeURIComponent(csv);
  a.download='complaints.csv';a.click();
}

function setView(v, el){
  document.querySelectorAll('.vt-btn').forEach(b=>b.classList.remove('active'));
  el.classList.add('active');
}

// init
updateCounts();
applyFilters();
</script>
</body>
</html>

Youez - 2016 - github.com/yon3zu
LinuXploit