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/constituents.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ConstituencyOS – Constituents</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;}
.s-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;}
.s-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 input{border:none;background:transparent;outline:none;width:100%;font-size:12px;font-family:inherit;}
.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;}

/* UTILS */
.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);}
.page-header{display:flex;align-items:flex-start;justify-content:space-between;}
.page-title{font-size:19px;font-weight:700;}
.page-sub{font-size:12px;color:var(--muted);margin-top:2px;}

/* 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:13px 15px;}
.sstat-val{font-size:22px;font-weight:700;line-height:1.1;}
.sstat-lbl{font-size:11px;color:var(--muted);margin-top:2px;}
.sstat-sub{font-size:10.5px;margin-top:4px;}

/* TOOLBAR */
.toolbar{display:flex;align-items:center;gap:10px;background:var(--white);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;flex-wrap:wrap;}
.search-bar{flex:1;min-width:200px;display:flex;align-items:center;gap:7px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:7px 11px;}
.search-bar input{border:none;background:transparent;outline:none;width:100%;font-size:12px;font-family:inherit;color:var(--text);}
.fsel{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;white-space:nowrap;}
.fsel 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{width:28px;height:26px;border-radius:4px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:13px;color:var(--muted);}
.vt.active{background:var(--white);color:var(--text);box-shadow:var(--shadow);}

/* CARD GRID */
.card-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;}
.constituent-card{background:var(--white);border:1px solid var(--border);border-radius:var(--radius);padding:16px;cursor:pointer;transition:box-shadow .15s;display:flex;flex-direction:column;gap:10px;}
.constituent-card:hover{box-shadow:0 4px 16px rgba(0,0,0,.1);border-color:var(--green-mid);}
.cc-top{display:flex;align-items:flex-start;gap:10px;}
.cc-avatar{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:17px;font-weight:700;color:white;flex-shrink:0;}
.cc-name{font-size:13px;font-weight:700;line-height:1.2;}
.cc-id{font-size:10px;color:var(--muted);margin-top:2px;}
.cc-ward{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;background:var(--green-light);color:var(--green);padding:2px 8px;border-radius:20px;font-weight:600;}
.cc-meta{display:flex;flex-direction:column;gap:4px;}
.cc-meta-row{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--muted);}
.cc-meta-row strong{color:var(--text);}
.cc-tags{display:flex;flex-wrap:wrap;gap:4px;}
.cc-tag{font-size:9.5px;padding:2px 7px;border-radius:20px;font-weight:600;background:var(--bg);color:var(--muted);border:1px solid var(--border);}
.cc-tag.voter{background:var(--blue-light);color:var(--blue);border-color:#bfdbfe;}
.cc-tag.bursary{background:var(--green-light);color:var(--green);border-color:#bbf7d0;}
.cc-tag.complaint{background:var(--amber-light);color:#92400e;border-color:#fde68a;}
.cc-tag.social{background:var(--purple-light);color:var(--purple);border-color:#ddd6fe;}
.cc-footer{display:flex;align-items:center;justify-content:space-between;padding-top:8px;border-top:1px solid var(--border);}
.cc-phone{font-size:11px;color:var(--muted);}
.cc-action{font-size:11px;color:var(--green);font-weight:600;cursor:pointer;}

/* TABLE */
.table-card{background:var(--white);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:none;}
.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);}
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;}

/* 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;}
.detail-overlay.open{display:flex;align-items:flex-start;justify-content:flex-end;}
.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{padding:16px 18px;border-bottom:1px solid var(--border);flex-shrink:0;display:flex;align-items:center;gap:14px;}
.dp-avatar{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:700;color:white;flex-shrink:0;}
.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;margin-left:auto;flex-shrink:0;}
.dp-name{font-size:15px;font-weight:700;}
.dp-sub{font-size:11.5px;color:var(--muted);margin-top:2px;}
.dp-body{flex:1;overflow-y:auto;padding:18px;display:flex;flex-direction:column;gap:16px;}
.dp-sect{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;max-width:60%;}
.activity-list{display:flex;flex-direction:column;gap:6px;}
.al-item{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;background:var(--bg);border-radius:var(--radius-sm);font-size:11.5px;}
.al-icon{font-size:14px;flex-shrink:0;}
.al-text{flex:1;line-height:1.4;}
.al-date{font-size:10px;color:var(--muted);margin-top:2px;}
.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;}

/* EMPTY STATE */
.empty{text-align:center;padding:60px 20px;color:var(--muted);}
.empty-icon{font-size:40px;margin-bottom:12px;}
.empty-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:6px;}
.empty-sub{font-size:12px;}
</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="s-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…"><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="s-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">

    <div class="page-header">
      <div>
        <div class="page-title">Constituents</div>
        <div class="page-sub">Resident directory for Embakasi South β€” all wards</div>
      </div>
      <div style="display:flex;gap:8px;">
        <button class="btn btn-ghost">⬇ Export</button>
        <button class="btn btn-primary">οΌ‹ Add Constituent</button>
      </div>
    </div>

    <!-- Stat Strip -->
    <div class="stat-strip">
      <div class="sstat">
        <div class="sstat-val">124,850</div>
        <div class="sstat-lbl">Total Residents</div>
        <div class="sstat-sub" style="color:var(--green)">β–² 3.2% this year</div>
      </div>
      <div class="sstat">
        <div class="sstat-val" style="color:var(--blue)">89,240</div>
        <div class="sstat-lbl">Registered Voters</div>
        <div class="sstat-sub" style="color:var(--muted)">71.5% of residents</div>
      </div>
      <div class="sstat">
        <div class="sstat-val" style="color:var(--purple)">3,248</div>
        <div class="sstat-lbl">Bursary Recipients</div>
        <div class="sstat-sub" style="color:var(--muted)">Active this year</div>
      </div>
      <div class="sstat">
        <div class="sstat-val" style="color:var(--amber)">1,024</div>
        <div class="sstat-lbl">Social Protection</div>
        <div class="sstat-sub" style="color:var(--muted)">Active beneficiaries</div>
      </div>
      <div class="sstat">
        <div class="sstat-val" style="color:var(--red)">23</div>
        <div class="sstat-lbl">Open Complaints</div>
        <div class="sstat-sub" style="color:var(--muted)">Awaiting resolution</div>
      </div>
    </div>

    <!-- Toolbar -->
    <div class="toolbar">
      <div class="search-bar">
        <span>πŸ”</span>
        <input type="text" id="searchInput" placeholder="Search by name, ID, phone, ward…" oninput="applyFilters()">
      </div>
      <div class="fsel">🏘️ <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="fsel">πŸ‘€ <select id="genderFilter" onchange="applyFilters()">
        <option value="">All Genders</option><option>Male</option><option>Female</option>
      </select></div>
      <div class="fsel">🏷️ <select id="tagFilter" onchange="applyFilters()">
        <option value="">All Tags</option>
        <option>Registered Voter</option><option>Bursary Recipient</option>
        <option>Has Complaint</option><option>Social Protection</option>
      </select></div>
      <div class="fsel">↕ <select id="sortFilter" onchange="applyFilters()">
        <option value="name">Name A–Z</option>
        <option value="ward">Ward</option>
        <option value="recent">Recently Added</option>
      </select></div>
      <div class="view-toggle">
        <div class="vt active" id="vt-cards" onclick="setView('cards')" title="Cards">⊞</div>
        <div class="vt" id="vt-table" onclick="setView('table')" title="Table">☰</div>
      </div>
    </div>

    <!-- Card Grid -->
    <div class="card-grid" id="cardGrid"></div>

    <!-- Table View -->
    <div class="table-card" id="tableView">
      <div class="table-wrap">
        <table>
          <thead><tr>
            <th>ID</th><th>Name</th><th>Ward</th><th>Gender</th>
            <th>Phone</th><th>Occupation</th><th>Tags</th><th>Registered</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>

    <!-- Pagination for cards -->
    <div class="pagination" id="cardPagination" style="background:var(--white);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;">
      <div class="pg-info" id="pgInfoCards"></div>
      <div class="pg-btns" id="pgBtnsCards"></div>
    </div>

    <!-- Empty State -->
    <div class="empty" id="emptyState" style="display:none;">
      <div class="empty-icon">πŸ‘₯</div>
      <div class="empty-title">No constituents found</div>
      <div class="empty-sub">Try adjusting your search or filters</div>
    </div>

  </main>
</div>

<!-- DETAIL PANEL -->
<div class="detail-overlay" id="detailOverlay" onclick="closeDetail(event)">
  <div class="detail-panel">
    <div class="dp-header">
      <div class="dp-avatar" id="dp-avatar"></div>
      <div>
        <div class="dp-name" id="dp-name"></div>
        <div class="dp-sub" id="dp-sub"></div>
      </div>
      <div class="dp-close" onclick="closeDetailDirect()">βœ•</div>
    </div>
    <div class="dp-body">
      <div>
        <div class="dp-sect">Personal Information</div>
        <div class="dp-row"><span class="dp-key">ID Number</span><span class="dp-val" id="dp-idno"></span></div>
        <div class="dp-row"><span class="dp-key">Gender</span><span class="dp-val" id="dp-gender"></span></div>
        <div class="dp-row"><span class="dp-key">Date of Birth</span><span class="dp-val" id="dp-dob"></span></div>
        <div class="dp-row"><span class="dp-key">Phone</span><span class="dp-val" id="dp-phone"></span></div>
        <div class="dp-row"><span class="dp-key">Email</span><span class="dp-val" id="dp-email"></span></div>
        <div class="dp-row"><span class="dp-key">Occupation</span><span class="dp-val" id="dp-occ"></span></div>
      </div>
      <div>
        <div class="dp-sect">Residence</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">Village / Estate</span><span class="dp-val" id="dp-village"></span></div>
        <div class="dp-row"><span class="dp-key">Registered Voter</span><span class="dp-val" id="dp-voter"></span></div>
        <div class="dp-row"><span class="dp-key">Polling Station</span><span class="dp-val" id="dp-polling"></span></div>
      </div>
      <div>
        <div class="dp-sect">Constituency Engagement</div>
        <div class="activity-list" id="dp-activity"></div>
      </div>
      <div>
        <div class="dp-sect">Notes</div>
        <textarea id="dp-notes" style="width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 10px;font-size:12px;font-family:inherit;resize:none;height:72px;outline:none;color:var(--text);" placeholder="Add a note about this constituent…" onfocus="this.style.borderColor='var(--green)'" onblur="this.style.borderColor='var(--border)'"></textarea>
        <button class="btn btn-ghost" style="margin-top:6px;font-size:11.5px;" onclick="saveNote()">Save Note</button>
      </div>
    </div>
    <div class="dp-footer">
      <button class="btn btn-ghost" style="flex:1;justify-content:center;" onclick="closeDetailDirect()">Close</button>
      <button class="btn btn-primary" style="flex:1;justify-content:center;">πŸ“‹ Log Interaction</button>
    </div>
  </div>
</div>

<script>
const AVATAR_COLORS = ['#1a7a4a','#2563eb','#7c3aed','#dc2626','#d97706','#0891b2','#be185d','#065f46'];
function avatarColor(name){ let h=0; for(let c of name) h=(h*31+c.charCodeAt(0))%AVATAR_COLORS.length; return AVATAR_COLORS[h]; }
function initials(name){ return name.split(' ').slice(0,2).map(n=>n[0]).join('').toUpperCase(); }

const CONSTITUENTS = [
  {id:'CNS-00124',name:'Jane Wanjiku Mwangi',ward:'Pipeline Ward',village:'Kiandutu Estate',gender:'Female',dob:'14 Mar 1985',phone:'0712 345 678',email:'jane.mwangi@gmail.com',occ:'Teacher',voter:true,polling:'Kiandutu Primary School',tags:['Registered Voter','Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1428 β€” Water pipe burst',date:'20 May 2024'},{icon:'πŸŽ“',text:'Bursary application approved for son Kevin, Form 4',date:'Jan 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-00389',name:'Peter Ochieng Otieno',ward:'Imara Daima',village:'Kwa Reuben',gender:'Male',dob:'22 Jun 1979',phone:'0723 456 789',email:'p.otieno@yahoo.com',occ:'Matatu Operator',voter:true,polling:'Imara Daima Primary',tags:['Registered Voter','Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1427 β€” Broken bridge',date:'19 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-00512',name:'Mary Akinyi Odhiambo',ward:'Mabatini Ward',village:'Soweto',gender:'Female',dob:'3 Nov 1992',phone:'0734 567 890',email:'mary.akinyi@gmail.com',occ:'Nurse',voter:true,polling:'Mabatini Social Hall',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Received bursary KSh 25,000 β€” Kenya Medical Training College',date:'Mar 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-00671',name:'David Otieno Omondi',ward:'Umoja Ward',village:'Umoja II',gender:'Male',dob:'18 Aug 1967',phone:'0745 678 901',email:'',occ:'Small Business Owner',voter:true,polling:'Umoja Primary School',tags:['Registered Voter','Has Complaint','Social Protection'],activity:[{icon:'πŸ›‘οΈ',text:'Enrolled in Social Protection Programme β€” Elderly Support',date:'Jan 2024'},{icon:'πŸ“‹',text:'Filed complaint #1425 β€” Garbage collection',date:'18 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-00834',name:'Grace Muthoni Njoroge',ward:'Kwa Njenga Ward',village:'Kware',gender:'Female',dob:'7 Feb 1990',phone:'0756 789 012',email:'grace.njoroge@gmail.com',occ:'Hairdresser',voter:false,polling:'',tags:['Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1424 β€” Drainage blockage',date:'17 May 2024'}],notes:''},
  {id:'CNS-00945',name:'Samuel Njoroge Kariuki',ward:'Mihango Ward',village:'Mihango Estate',gender:'Male',dob:'29 Oct 1988',phone:'0767 890 123',email:'sam.kariuki@gmail.com',occ:'Boda Boda Rider',voter:true,polling:'Mihango Primary School',tags:['Registered Voter','Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1423 β€” Road pothole Mihango Rd',date:'17 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-01102',name:'Ann Chebet Kosgei',ward:'Pipeline Ward',village:'Pipeline Estate',gender:'Female',dob:'11 Apr 1995',phone:'0778 901 234',email:'ann.chebet@gmail.com',occ:'Student',voter:true,polling:'Pipeline Primary School',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Received bursary KSh 35,000 β€” Kenyatta University',date:'Feb 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-01234',name:'Paul Mutua Nzomo',ward:'Imara Daima',village:'Imara Daima Estate',gender:'Male',dob:'5 Dec 1972',phone:'0789 012 345',email:'paul.mutua@yahoo.com',occ:'Security Guard',voter:true,polling:'Imara Daima Secondary',tags:['Registered Voter','Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1421 β€” Health clinic out of stock',date:'16 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-01378',name:'Rose Kamau Wanjiru',ward:'Mabatini Ward',village:'Mabatini',gender:'Female',dob:'23 Jul 1983',phone:'0790 123 456',email:'rose.kamau@gmail.com',occ:'Farmer',voter:true,polling:'Mabatini Primary',tags:['Registered Voter','Has Complaint','Social Protection'],activity:[{icon:'πŸ›‘οΈ',text:'Enrolled β€” PWD Support Programme',date:'Nov 2023'},{icon:'πŸ“‹',text:'Filed complaint #1420 β€” Borehole pump failure',date:'15 May 2024'}],notes:''},
  {id:'CNS-01489',name:'Kevin Omondi Adhiambo',ward:'Kwa Njenga Ward',village:'Kwa Njenga',gender:'Male',dob:'14 Jan 2001',phone:'0701 234 567',email:'kevin.omondi@gmail.com',occ:'Student',voter:true,polling:'Kwa Njenga Primary',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Bursary application under review β€” Nairobi Technical Training Institute',date:'4 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-01567',name:'Fatuma Hassan Omar',ward:'Kwa Njenga Ward',village:'Kwa Njenga Market',gender:'Female',dob:'9 Sep 1986',phone:'0712 567 890',email:'fatuma.hassan@gmail.com',occ:'Trader',voter:false,polling:'',tags:['Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1419 β€” Illegal dumping river bank',date:'15 May 2024'}],notes:''},
  {id:'CNS-01689',name:'James Mburu Njoroge',ward:'Umoja Ward',village:'Umoja I',gender:'Male',dob:'30 Mar 1975',phone:'0723 678 901',email:'james.mburu@gmail.com',occ:'Electrician',voter:true,polling:'Umoja Secondary School',tags:['Registered Voter','Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1417 β€” Transformer overloading',date:'14 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-01801',name:'Nancy Wangari Kamau',ward:'Pipeline Ward',village:'Pipeline Rd Area',gender:'Female',dob:'17 Jun 1980',phone:'0734 789 012',email:'nancy.wangari@gmail.com',occ:'Primary School Teacher',voter:true,polling:'Kiandutu Primary School',tags:['Registered Voter','Has Complaint'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1416 β€” School leaking roof',date:'13 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-01923',name:'Lucy Wambua Mutua',ward:'Imara Daima',village:'Imara Daima',gender:'Female',dob:'25 Aug 1991',phone:'0745 890 123',email:'lucy.wambua@gmail.com',occ:'Self-employed',voter:true,polling:'Imara Daima Primary',tags:['Registered Voter'],activity:[{icon:'πŸ“‹',text:'Filed complaint #1415 β€” Noise pollution quarry',date:'13 May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02045',name:'John Kariuki Njeru',ward:'Mabatini Ward',village:'Mabatini South',gender:'Male',dob:'2 Feb 1969',phone:'0756 901 234',email:'',occ:'Retired Civil Servant',voter:true,polling:'Mabatini Social Hall',tags:['Registered Voter','Social Protection'],activity:[{icon:'πŸ›‘οΈ',text:'Enrolled β€” Elderly Support Programme',date:'Oct 2023'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02167',name:'Esther Wanjiru Gitau',ward:'Mihango Ward',village:'Mihango',gender:'Female',dob:'12 Dec 1999',phone:'0767 012 345',email:'esther.gitau@gmail.com',occ:'Vocational Student',voter:true,polling:'Mihango Primary School',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Bursary application β€” Nyeri National Polytechnic',date:'25 Apr 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02289',name:'Michael Oduya Ongeri',ward:'Umoja Ward',village:'Umoja II Zone 4',gender:'Male',dob:'20 May 1993',phone:'0778 123 456',email:'michael.oduya@gmail.com',occ:'IT Technician',voter:true,polling:'Umoja Primary School',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Received bursary KSh 40,000 β€” Kabarak University',date:'Apr 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02401',name:'Beatrice Muthoni Waweru',ward:'Kwa Njenga Ward',village:'Kware South',gender:'Female',dob:'8 Jul 1987',phone:'0789 234 567',email:'beatrice.waweru@gmail.com',occ:'Nurse',voter:true,polling:'Kwa Njenga Secondary',tags:['Registered Voter'],activity:[{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02523',name:'Vincent Mwenda Kirimi',ward:'Pipeline Ward',village:'Pipeline North',gender:'Male',dob:'15 Nov 1997',phone:'0790 345 678',email:'vincent.mwenda@gmail.com',occ:'University Student',voter:true,polling:'Pipeline Primary School',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Received bursary KSh 34,000 β€” Chuka University',date:'Apr 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02645',name:'Amina Wario Hassan',ward:'Imara Daima',village:'Imara Daima West',gender:'Female',dob:'3 Apr 1996',phone:'0712 456 789',email:'amina.wario@gmail.com',occ:'Medical Student',voter:true,polling:'Imara Daima Primary',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Received bursary KSh 50,000 β€” University of Nairobi',date:'May 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02767',name:'Daniel Kipchoge Rono',ward:'Pipeline Ward',village:'Kiandutu',gender:'Male',dob:'6 Jun 1998',phone:'0723 789 012',email:'daniel.rono@gmail.com',occ:'Medical Student',voter:true,polling:'Kiandutu Primary School',tags:['Registered Voter','Bursary Recipient'],activity:[{icon:'πŸŽ“',text:'Received bursary KSh 28,000 β€” KMTC',date:'Mar 2024'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-02889',name:'Hannah Ongeri Odhiambo',ward:'Umoja Ward',village:'Umoja I',gender:'Female',dob:'27 Sep 1970',phone:'0734 890 123',email:'',occ:'Market Trader',voter:true,polling:'Umoja Secondary School',tags:['Registered Voter','Social Protection'],activity:[{icon:'πŸ›‘οΈ',text:'Enrolled β€” Orphans & Vulnerable Programme',date:'Dec 2023'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-03001',name:'Elijah Rono Kipchoge',ward:'Pipeline Ward',village:'Pipeline South',gender:'Male',dob:'19 Jan 1965',phone:'0745 901 234',email:'',occ:'Retired',voter:true,polling:'Pipeline Primary School',tags:['Registered Voter','Social Protection'],activity:[{icon:'πŸ›‘οΈ',text:'Enrolled β€” Elderly Support Programme',date:'Sep 2023'},{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
  {id:'CNS-03123',name:'Susan Wanjiku Njeru',ward:'Imara Daima',village:'Kwa Reuben',gender:'Female',dob:'11 Mar 1994',phone:'0756 012 345',email:'susan.wanjiku@gmail.com',occ:'Accountant',voter:true,polling:'Imara Daima Secondary',tags:['Registered Voter'],activity:[{icon:'πŸ—³οΈ',text:'Voted in 2022 General Election',date:'Aug 2022'}],notes:''},
];

let filtered = [...CONSTITUENTS];
let currentView = 'cards';
let currentPage = 1;
const PAGE_SIZE = 12;
let currentConstituent = null;

function tagHtml(tags){
  return tags.map(t=>{
    const cls = t==='Registered Voter'?'voter':t==='Bursary Recipient'?'bursary':t==='Has Complaint'?'complaint':'social';
    const icon = t==='Registered Voter'?'πŸ—³οΈ':t==='Bursary Recipient'?'πŸŽ“':t==='Has Complaint'?'πŸ“‹':'πŸ›‘οΈ';
    return `<span class="cc-tag ${cls}">${icon} ${t}</span>`;
  }).join('');
}

function applyFilters(){
  const q = document.getElementById('searchInput').value.toLowerCase();
  const ward = document.getElementById('wardFilter').value;
  const gender = document.getElementById('genderFilter').value;
  const tag = document.getElementById('tagFilter').value;
  const sort = document.getElementById('sortFilter').value;

  filtered = CONSTITUENTS.filter(c=>{
    if(ward && c.ward !== ward) return false;
    if(gender && c.gender !== gender) return false;
    if(tag && !c.tags.includes(tag)) return false;
    if(q && !`${c.id} ${c.name} ${c.ward} ${c.village} ${c.phone} ${c.occ}`.toLowerCase().includes(q)) return false;
    return true;
  });

  if(sort==='name') filtered.sort((a,b)=>a.name.localeCompare(b.name));
  else if(sort==='ward') filtered.sort((a,b)=>a.ward.localeCompare(b.ward));
  else filtered.sort((a,b)=>parseInt(b.id.split('-')[1])-parseInt(a.id.split('-')[1]));

  currentPage = 1;
  render();
}

function setView(v){
  currentView = v;
  document.getElementById('vt-cards').classList.toggle('active', v==='cards');
  document.getElementById('vt-table').classList.toggle('active', v==='table');
  document.getElementById('cardGrid').style.display = v==='cards'?'grid':'none';
  document.getElementById('cardPagination').style.display = v==='cards'?'flex':'none';
  document.getElementById('tableView').style.display = v==='table'?'block':'none';
  render();
}

function render(){
  const start = (currentPage-1)*PAGE_SIZE, end = start+PAGE_SIZE;
  const page = filtered.slice(start, end);
  const isEmpty = filtered.length === 0;
  document.getElementById('emptyState').style.display = isEmpty ? 'block' : 'none';
  document.getElementById('cardGrid').style.display = (!isEmpty && currentView==='cards') ? 'grid' : 'none';
  document.getElementById('cardPagination').style.display = (!isEmpty && currentView==='cards') ? 'flex' : 'none';
  document.getElementById('tableView').style.display = (!isEmpty && currentView==='table') ? 'block' : 'none';

  if(currentView==='cards') renderCards(page, start, end);
  else renderTable(page, start, end);
}

function renderCards(page, start, end){
  document.getElementById('cardGrid').innerHTML = page.map(c=>`
    <div class="constituent-card" onclick="openDetail('${c.id}')">
      <div class="cc-top">
        <div class="cc-avatar" style="background:${avatarColor(c.name)}">${initials(c.name)}</div>
        <div style="flex:1;min-width:0;">
          <div class="cc-name">${c.name}</div>
          <div class="cc-id">${c.id}</div>
          <div style="margin-top:5px"><span class="cc-ward">πŸ“ ${c.ward}</span></div>
        </div>
      </div>
      <div class="cc-meta">
        <div class="cc-meta-row">πŸ’Ό <strong>${c.occ}</strong></div>
        <div class="cc-meta-row">🏘️ <strong>${c.village}</strong></div>
      </div>
      <div class="cc-tags">${tagHtml(c.tags)}</div>
      <div class="cc-footer">
        <span class="cc-phone">πŸ“ž ${c.phone}</span>
        <span class="cc-action">View profile β†’</span>
      </div>
    </div>
  `).join('');

  const total = filtered.length;
  document.getElementById('pgInfoCards').textContent = `Showing ${Math.min(start+1,total)}–${Math.min(end,total)} of ${total} constituents`;
  renderPagination('pgBtnsCards', total);
}

function renderTable(page, start, end){
  document.getElementById('tableBody').innerHTML = page.map(c=>`
    <tr onclick="openDetail('${c.id}')">
      <td style="font-size:11px;color:var(--muted);font-weight:600">${c.id}</td>
      <td>
        <div style="display:flex;align-items:center;gap:8px">
          <div style="width:30px;height:30px;border-radius:50%;background:${avatarColor(c.name)};color:white;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0">${initials(c.name)}</div>
          <div><div style="font-weight:600">${c.name}</div><div style="font-size:10.5px;color:var(--muted)">${c.village}</div></div>
        </div>
      </td>
      <td style="font-size:11.5px">${c.ward}</td>
      <td style="font-size:11.5px">${c.gender}</td>
      <td style="font-size:11.5px">${c.phone}</td>
      <td style="font-size:11.5px">${c.occ}</td>
      <td>${tagHtml(c.tags)}</td>
      <td style="font-size:11px;color:var(--muted)">${c.id.split('-')[1]}</td>
    </tr>
  `).join('');

  const total = filtered.length;
  document.getElementById('pgInfo').textContent = `Showing ${Math.min(start+1,total)}–${Math.min(end,total)} of ${total}`;
  renderPagination('pgBtns', total);
}

function renderPagination(containerId, total){
  const pages = Math.ceil(total/PAGE_SIZE);
  const pb = document.getElementById(containerId);
  pb.innerHTML = '';
  const add = (l,p,a) => { const d=document.createElement('div'); d.className='pg-btn'+(a?' active':''); d.textContent=l; d.onclick=()=>{currentPage=p;render();}; pb.appendChild(d); };
  if(currentPage>1) add('β€Ή',currentPage-1,false);
  for(let i=1;i<=Math.min(pages,7);i++) add(i,i,i===currentPage);
  if(currentPage<pages) add('β€Ί',currentPage+1,false);
}

function openDetail(id){
  currentConstituent = CONSTITUENTS.find(c=>c.id===id);
  const c = currentConstituent;
  const color = avatarColor(c.name);
  document.getElementById('dp-avatar').textContent = initials(c.name);
  document.getElementById('dp-avatar').style.background = color;
  document.getElementById('dp-name').textContent = c.name;
  document.getElementById('dp-sub').innerHTML = `<span style="background:${color}20;color:${color};padding:2px 8px;border-radius:20px;font-size:11px;font-weight:600">${c.ward}</span>`;
  document.getElementById('dp-idno').textContent = c.id;
  document.getElementById('dp-gender').textContent = c.gender;
  document.getElementById('dp-dob').textContent = c.dob;
  document.getElementById('dp-phone').textContent = c.phone || 'β€”';
  document.getElementById('dp-email').textContent = c.email || 'β€”';
  document.getElementById('dp-occ').textContent = c.occ;
  document.getElementById('dp-ward').textContent = c.ward;
  document.getElementById('dp-village').textContent = c.village;
  document.getElementById('dp-voter').textContent = c.voter ? 'βœ“ Yes' : 'βœ— No';
  document.getElementById('dp-polling').textContent = c.polling || 'β€”';
  document.getElementById('dp-activity').innerHTML = c.activity.map(a=>`
    <div class="al-item">
      <span class="al-icon">${a.icon}</span>
      <div><div class="al-text">${a.text}</div><div class="al-date">${a.date}</div></div>
    </div>
  `).join('');
  document.getElementById('dp-notes').value = c.notes || '';
  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 saveNote(){
  if(currentConstituent){
    currentConstituent.notes = document.getElementById('dp-notes').value;
    const btn = event.target;
    btn.textContent = 'βœ“ Saved'; btn.style.color='var(--green)';
    setTimeout(()=>{ btn.textContent='Save Note'; btn.style.color=''; }, 1500);
  }
}

// Initial sort & render
document.getElementById('sortFilter').value = 'name';
applyFilters();
</script>
</body>
</html>

Youez - 2016 - github.com/yon3zu
LinuXploit