nhance/app/Views/sales/branch_level_dashboard_view.php
2026-03-07 10:38:06 +05:30

796 lines
46 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
.dash-container {
padding: 28px;
background: #f4f6fb;
font-family: 'Plus Jakarta Sans', 'Segoe UI', sans-serif;
min-height: 100vh;
}
/* ── Dashboard Header ── */
.dash-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 12px;
}
.page-title { font-size: 20px; font-weight: 800; color: #0f172a; margin: 0 0 4px 0; }
.page-sub { font-size: 13px; color: #7c8db0; font-weight: 600; }
.fy-select-wrap select {
background: #fff;
padding: 10px 16px;
border-radius: 10px;
border: 1px solid #e4eaf5;
font-size: 13px;
font-weight: 700;
color: #0f172a;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
min-width: 200px;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%237c8db0' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 14px center;
padding-right: 36px;
}
.fy-select-wrap select:focus { outline: none; border-color: #4f46e5; }
/* ── Stat Cards ── */
.stat-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
margin-bottom: 24px;
}
.card-hero {
background: white;
padding: 22px 24px;
border-radius: 14px;
border: 1px solid #e8edf5;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
position: relative;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card-hero::before {
content: "";
position: absolute;
top: 0; left: 0;
width: 100%; height: 4px;
}
.card-hero:nth-child(1)::before { background: linear-gradient(90deg, #667eea, #764ba2); }
.card-hero:nth-child(2)::before { background: linear-gradient(90deg, #f093fb, #f5576c); }
.card-hero:nth-child(3)::before { background: linear-gradient(90deg, #fda085, #f6d365); }
.card-hero:nth-child(4)::before { background: linear-gradient(90deg, #43e97b, #38f9d7); }
.card-hero:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0,0,0,0.09); }
.stat-icon { width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; margin-bottom: 14px; }
.stat-val { font-size: 30px; font-weight: 800; color: #1a202c; line-height: 1; }
.stat-label { color: #8896aa; font-size: 13px; margin-top: 6px; font-weight: 600; }
/* ── Shared Card ── */
.card { background: white; padding: 24px; border-radius: 14px; border: 1px solid #e8edf5; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }
/* ── Main Grid ── */
.main-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 24px; align-items: start; }
.main-grid > .card { height: 520px; display: flex; flex-direction: column; }
.table-scroll-area { flex: 1; overflow-y: auto; }
.table-scroll-area thead th { position: sticky; top: 0; background: white; z-index: 1; }
/* ── Right Panel ── */
.right-panel { height: 520px; background: white; border-radius: 14px; border: 1px solid #e8edf5; box-shadow: 0 2px 8px rgba(0,0,0,0.04); padding: 22px; box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; }
.team-members-scroll { flex: 1; overflow-y: auto; margin-top: 10px; }
.team-member { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #f4f6fb; }
.team-member:last-child { border-bottom: none; }
.member-img { width: 38px; height: 38px; border-radius: 50%; color: white; margin-right: 12px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px; flex-shrink: 0; }
.act-stats { text-align: right; }
.act-stats .total { font-weight: 700; font-size: 13px; color: #1a202c; }
.act-stats .done { font-size: 11px; color: #38a169; font-weight: 600; margin-top: 2px; }
/* ── Activity Breakdown ── */
.breakdown-section { flex-shrink: 0; margin-top: 12px; border: 1px solid #f0f4fa; border-radius: 12px; padding: 14px; background: #fafbfd; }
.breakdown-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 9px; font-size: 13px; }
.breakdown-item:last-child { margin-bottom: 0; }
.breakdown-bar-wrap { flex: 1; height: 5px; background: #eef0f5; border-radius: 99px; margin: 0 10px; overflow: hidden; }
.breakdown-bar { height: 100%; border-radius: 99px; }
/* ── Table ── */
.table-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; padding: 10px 12px; color: #8896aa; font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; border-bottom: 1px solid #edf2f7; background: white; font-weight: 700; }
td { padding: 13px 12px; border-bottom: 1px solid #f4f6fb; font-size: 14px; vertical-align: middle; }
tbody tr:hover { background: #fafbfe; }
/* ── Status Pills ── */
.status-pill { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 700; display: inline-block; }
.status-completed { background: #f0fff4; color: #38a169; }
.status-pending { background: #fffaf0; color: #dd6b20; }
.status-new { background: #e3f2fd; color: #1976d2; }
.status-potential { background: #fff3e0; color: #f57c00; }
.status-prospects { background: #e8f5e9; color: #388e3c; }
.status-not-a-prospects { background: #ffebee; color: #d32f2f; }
.leads-table-wrap { max-height: 480px; overflow-y: auto; }
.leads-table-wrap thead th { position: sticky; top: 0; background: white; z-index: 1; }
.empty-state { text-align: center; padding: 50px 20px; color: #aaa; }
.empty-icon { width: 70px; height: 70px; margin: 0 auto 16px; background: #f5f5f5; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 32px; }
/* ── Responsive ── */
@media (max-width: 1200px) { .stat-cards { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 992px) { .main-grid { grid-template-columns: 1fr; } .main-grid > .card, .right-panel { height: 450px; } .stat-cards { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 576px) { .dash-container { padding: 14px; } .stat-cards { grid-template-columns: 1fr 1fr; gap: 10px; } .stat-val { font-size: 24px; } .main-grid > .card, .right-panel { height: 400px; } table th, table td { padding: 10px 8px; font-size: 12px; } .dash-header { flex-direction: column; align-items: flex-start; } }
/* ══ TEAM ACHIEVEMENT SECTION ══ */
.section-head { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 16px; }
.section-title { font-size: 16px; font-weight: 800; color: #0f172a; }
.section-sub { font-size: 12px; color: #7c8db0; font-weight: 600; margin-top: 3px; }
.branch-summary { text-align: right; }
.branch-amt { font-size: 20px; font-weight: 800; color: #0f172a; }
.branch-bar-wrap { width: 180px; height: 5px; background: #e4eaf5; border-radius: 99px; overflow: hidden; margin: 6px 0 4px auto; }
.branch-bar-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg,#4f46e5,#818cf8); }
.branch-pct-lbl { font-size: 11px; color: #4f46e5; font-weight: 700; }
.achieve-list { background: #fff; border-radius: 14px; border: 1px solid #e4eaf5; box-shadow: 0 2px 12px rgba(15,23,42,0.06); overflow: hidden; }
.list-col-head { display: grid; grid-template-columns: 260px 1fr 130px 130px 130px 110px 80px; padding: 10px 22px; background: #f8fafd; border-bottom: 1px solid #e4eaf5; }
.col-lbl { font-size: 10px; font-weight: 700; color: #7c8db0; letter-spacing: .07em; text-transform: uppercase; display: flex; align-items: center; }
.col-lbl.right { justify-content: flex-end; }
.col-lbl.center { justify-content: center; }
.achieve-row { display: grid; grid-template-columns: 260px 1fr 130px 130px 130px 110px 80px; padding: 16px 22px; border-bottom: 1px solid #f1f5f9; align-items: center; cursor: pointer; transition: background .15s; position: relative; }
.achieve-row:last-child { border-bottom: none; }
.achieve-row:hover { background: #f8fafd; }
.achieve-row:hover .row-arrow { opacity: 1; transform: translateY(-50%) translateX(0); }
.member-cell { display: flex; align-items: center; gap: 12px; }
.m-avatar { width: 42px; height: 42px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 16px; color: #fff; flex-shrink: 0; }
.m-name { font-weight: 700; font-size: 14px; color: #0f172a; }
.m-role { font-size: 11px; color: #7c8db0; font-weight: 600; margin-top: 2px; }
.prog-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
.prog-pct { font-size: 13px; font-weight: 800; }
.prog-amts { font-size: 11px; color: #7c8db0; font-weight: 600; }
.prog-bar-wrap { height: 6px; background: #f0f3fa; border-radius: 99px; overflow: hidden; }
.prog-bar { height: 100%; border-radius: 99px; }
.num-cell { text-align: right; }
.num-val { font-size: 15px; font-weight: 800; }
.num-lbl { font-size: 10px; color: #7c8db0; font-weight: 700; margin-top: 2px; letter-spacing: .04em; }
.num-cell.center-col { text-align: center; }
.badge { display: inline-flex; align-items: center; gap: 5px; padding: 4px 12px; border-radius: 99px; font-size: 11px; font-weight: 700; white-space: nowrap; }
.b-achieved { background: #ecfdf5; color: #059669; }
.b-ontrack { background: #eff6ff; color: #2563eb; }
.b-behind { background: #fff7ed; color: #c2410c; }
.b-atrisk { background: #fef2f2; color: #b91c1c; }
.row-arrow { position: absolute; right: 18px; top: 50%; transform: translateY(-50%) translateX(4px); color: #cbd5e1; font-size: 16px; opacity: 0; transition: opacity .15s, transform .15s; }
/* ══ MODAL ══ */
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(10,15,30,0.55); backdrop-filter: blur(6px); z-index: 1000; align-items: center; justify-content: center; padding: 20px; }
.modal-overlay.open { display: flex; animation: fadeIn .2s ease; }
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
.modal-box { background: #fff; border-radius: 20px; width: 100%; max-width: 780px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 32px 80px rgba(15,23,42,0.25); animation: slideUp .25s ease; }
@keyframes slideUp { from { transform: translateY(30px); opacity: 0 } to { transform: translateY(0); opacity: 1 } }
.modal-header { padding: 20px 24px 16px; border-bottom: 1px solid #e4eaf5; display: flex; align-items: center; gap: 14px; flex-shrink: 0; }
.modal-ava { width: 50px; height: 50px; border-radius: 14px; display: flex; align-items: center; justify-content: center; font-size: 21px; font-weight: 800; color: #fff; flex-shrink: 0; }
.modal-name { font-size: 17px; font-weight: 800; }
.modal-fy-badge { font-size: 11px; font-weight: 700; background: #eff6ff; color: #2563eb; padding: 3px 10px; border-radius: 99px; margin-top: 4px; display: inline-block; }
.modal-close { margin-left: auto; background: #f1f5f9; border: none; width: 34px; height: 34px; border-radius: 50%; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center; color: #7c8db0; transition: background .2s; }
.modal-close:hover { background: #e2e8f0; color: #0f172a; }
.modal-summary { display: grid; grid-template-columns: repeat(4,1fr); background: #f8fafd; border-bottom: 1px solid #e4eaf5; flex-shrink: 0; }
.ms-box { padding: 14px 18px; text-align: center; border-right: 1px solid #e4eaf5; }
.ms-box:last-child { border-right: none; }
.ms-num { font-size: 19px; font-weight: 800; }
.ms-lbl { font-size: 10px; color: #7c8db0; font-weight: 700; letter-spacing: .05em; margin-top: 2px; }
.modal-body { padding: 22px 24px; overflow-y: auto; flex: 1; }
/* ── Modal Tabs ── */
.modal-tabs { display: flex; gap: 4px; background: #f1f5f9; border-radius: 10px; padding: 4px; margin-bottom: 20px; }
.modal-tab { flex: 1; padding: 9px 16px; border: none; border-radius: 8px; font-size: 13px; font-weight: 700; cursor: pointer; background: transparent; color: #7c8db0; transition: all .2s; }
.modal-tab.active { background: #fff; color: #0f172a; box-shadow: 0 2px 8px rgba(15,23,42,0.08); }
.modal-tab-panel { display: none; }
.modal-tab-panel.active { display: block; }
.overall-prog { background: linear-gradient(135deg,#0f172a,#1e293b); border-radius: 14px; padding: 18px 20px; color: #fff; margin-bottom: 20px; }
.op-label { font-size: 10px; font-weight: 700; letter-spacing: .08em; color: #94a3b8; }
.op-row { display: flex; justify-content: space-between; align-items: flex-end; margin: 6px 0 12px; }
.op-achieved { font-size: 24px; font-weight: 800; }
.op-target { font-size: 12px; color: #94a3b8; font-weight: 600; }
.op-pct { font-size: 26px; font-weight: 800; color: #818cf8; }
.op-bar-wrap { background: rgba(255,255,255,0.12); height: 7px; border-radius: 99px; overflow: hidden; }
.op-bar-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg,#818cf8,#67e8f9); }
.op-foot { display: flex; justify-content: space-between; font-size: 11px; color: #94a3b8; font-weight: 600; margin-top: 7px; }
.split-section-title { font-size: 13px; font-weight: 800; margin-bottom: 12px; }
.split-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.split-table thead th { background: #f8fafd; padding: 9px 12px; text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: .07em; color: #7c8db0; font-weight: 500; border-bottom: 2px solid #e4eaf5; }
.split-table thead th:last-child { text-align: right; }
.split-table tbody tr { border-bottom: 1px solid #f1f5f9; }
.split-table tbody tr:hover { background: #f8fafd; }
.split-table td { padding: 12px; vertical-align: middle; }
.split-table td:last-child { text-align: right; }
.split-name { font-weight: 700; font-size: 13px; }
.split-dates { font-size: 11px; color: #7c8db0; font-weight: 600; margin-top: 1px; }
.mini-prog-wrap { width: 80px; height: 4px; background: #e4eaf5; border-radius: 99px; overflow: hidden; margin-top: 4px; }
.mini-prog-bar { height: 100%; border-radius: 99px; }
.split-pct-badge { display: inline-block; padding: 3px 10px; border-radius: 99px; font-size: 11px; font-weight: 700; }
.act-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.act-chip { display: flex; align-items: center; gap: 6px; background: #f1f5f9; border-radius: 99px; padding: 5px 13px; font-size: 12px; font-weight: 700; }
.act-dot { width: 7px; height: 7px; border-radius: 50%; }
/* Opportunities tab */
.opp-summary-cards { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; margin-bottom: 20px; }
.opp-card { background: #f8fafd; border-radius: 12px; padding: 16px; border: 1px solid #e4eaf5; text-align: center; }
.opp-card-val { font-size: 22px; font-weight: 800; color: #0f172a; }
.opp-card-lbl { font-size: 11px; color: #7c8db0; font-weight: 700; margin-top: 4px; letter-spacing: .04em; }
@media(max-width:900px) { .list-col-head,.achieve-row { grid-template-columns: 220px 1fr 110px 110px 110px; } .col-lbl:nth-child(6),.col-lbl:nth-child(7),.achieve-row>*:nth-child(6),.achieve-row>*:nth-child(7) { display: none; } }
@media(max-width:640px) { .list-col-head { display: none; } .achieve-row { grid-template-columns: 1fr auto; gap: 12px; padding: 14px 16px; } .achieve-row>*:not(:nth-child(1)):not(:nth-child(7)) { display: none; } .section-head { flex-direction: column; align-items: flex-start; gap: 12px; } .modal-summary { grid-template-columns: repeat(2,1fr); } .opp-summary-cards { grid-template-columns: 1fr 1fr; } }
</style>
<div class="dash-container">
<!-- ── Dashboard Header ── -->
<div class="dash-header">
<div>
<div class="page-title">Branch Dashboard</div>
<div class="page-sub" id="fySubLabel"></div>
</div>
<div class="fy-select-wrap">
<select id="financial_year" name="financial_year" onchange="onFinancialYearChange(this)">
<?php if(!empty($fin_years)): ?>
<?php foreach($fin_years as $year): ?>
<option value="<?= $year; ?>"><?= $year; ?></option>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
</div>
<!-- ── Stat Cards ── -->
<div class="stat-cards">
<div class="card-hero">
<div class="stat-icon" style="background:#ede9fe;">👥</div>
<div class="stat-val"><?php echo $total_leads; ?></div>
<div class="stat-label">Total Leads</div>
</div>
<div class="card-hero">
<div class="stat-icon" style="background:#fce7f3;">⚡</div>
<div class="stat-val"><?php echo $total_acts ?></div>
<div class="stat-label">Total Activities</div>
</div>
<div class="card-hero">
<div class="stat-icon" style="background:#fef3c7;">⏳</div>
<div class="stat-val"><?php echo $total_pending_acts ?></div>
<div class="stat-label">Pending Activities</div>
</div>
<div class="card-hero">
<div class="stat-icon" style="background:#d1fae5;">✅</div>
<div class="stat-val"><?php echo $total_completed_acts ?></div>
<div class="stat-label">Completed Activities</div>
</div>
</div>
<!-- ══ TEAM ACHIEVEMENT SECTION ══ -->
<div class="section-head">
<div>
<div class="section-title">Team Achievement Overview</div>
<!-- <div class="section-sub">Click any row to view split-wise & opportunities achievement</div> -->
<div class="section-sub"></div>
</div>
<div class="branch-summary">
<div style="font-size:11px;color:#7c8db0;font-weight:700;">Branch Total</div>
<div class="branch-amt" id="branchTotalAmt">—</div>
<div class="branch-bar-wrap"><div class="branch-bar-fill" id="branchBarFill" style="width:0%;"></div></div>
<div class="branch-pct-lbl" id="branchPctLbl">—</div>
</div>
</div>
<div class="achieve-list">
<div class="list-col-head">
<div class="col-lbl">Member</div>
<div class="col-lbl">Progress</div>
<div class="col-lbl right">Achieved</div>
<div class="col-lbl right">Target</div>
<div class="col-lbl right">Remaining</div>
<div class="col-lbl center">Status</div>
<div class="col-lbl center">Acts</div>
</div>
<div id="achieveList"></div>
</div>
<br>
<!-- ── Main Grid ── -->
<div class="main-grid">
<!-- LEFT: Pending Activities -->
<div class="card">
<div class="table-header">
<h3 style="font-size:15px; font-weight:700; margin:0;">Pending Activities — All Team</h3>
<span style="color:#8896aa; font-size:12px; font-weight:600;">
<?= count($pending_acts) . ' ' . (count($pending_acts) == 1 ? 'activity' : 'activities') ?>
</span>
</div>
<div class="table-scroll-area">
<table>
<thead>
<tr>
<th>Company</th>
<th>Activity &amp; Assigned</th>
<th>Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if (!empty($pending_acts)): ?>
<?php foreach ($pending_acts as $a): ?>
<?php
$activityIcons = ['Call'=>'📞','Email'=>'✉️','Meeting'=>'📅','Visit'=>'🚗','Demo'=>'🖥️','Share Docs'=>'📄','To Do'=>'✓'];
$icon = $activityIcons[$a['activity_type']] ?? '📌';
$statusClass = strtolower(str_replace(' ', '-', $a['status']));
$formattedscheduledDate = date('M d, Y, h:i A', strtotime($a['scheduled_date']));
?>
<tr>
<td><strong><?= esc($a['company_name']) ?></strong></td>
<td>
<div style="color:#ff6b35; font-weight:700; font-size:11px;"><?= $icon ?> <?= strtoupper(esc($a['activity_type'])) ?></div>
<div style="font-size:12px; color:#8896aa;"><?= esc($a['assigned_to_name'] ?? 'ID: ' . $a['assigned_to']) ?></div>
</td>
<td style="color:#4a5568; font-weight:500; font-size:13px;"><?= $formattedscheduledDate ?></td>
<td><span class="status-pill status-<?= $statusClass ?>"><?= ucfirst(esc($a['status'])) ?></span></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="4"><div class="empty-state"><div class="empty-icon">✓</div>No pending activities</div></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- RIGHT: Sales Team Performance + Activity Breakdown -->
<div class="right-panel">
<div style="font-size:15px; font-weight:700; padding-bottom:10px; border-bottom:1px solid #f0f4fa; flex-shrink:0;">
Sales Team Performance
</div>
<div class="team-members-scroll">
<?php
if (!empty($team)):
$colors = ['#ff6b35','#667eea','#48bb78','#ed8936','#9f7aea'];
foreach ($team as $member):
$firstLetter = strtoupper(substr($member['first_name'], 0, 1));
$fullName = $member['first_name'];
$color = $colors[abs(crc32($member['first_name'])) % count($colors)];
?>
<div class="team-member">
<div class="member-img" style="background:<?= $color ?>;"><?= $firstLetter ?></div>
<div style="flex:1;">
<div style="font-weight:700; font-size:14px;"><?= esc($fullName) ?></div>
<div style="font-size:11px; color:#8896aa;"><?= esc($member['role']) ?></div>
</div>
<div class="act-stats">
<div class="total"><?= $member['total_acts'] ?> acts</div>
<div class="done"><?= $member['done_acts'] ?> done</div>
</div>
</div>
<?php endforeach; else: ?>
<div class="empty-state">No team data found</div>
<?php endif; ?>
</div>
<div class="breakdown-section">
<h3 style="font-size:13px; font-weight:700; margin:0 0 12px 0; color:#1a202c;">Activity Breakdown</h3>
<?php
$activityConfig = [
'Call' => ['color'=>'#667eea','icon'=>'📞'],
'Email' => ['color'=>'#48bb78','icon'=>'✉️'],
'Meeting' => ['color'=>'#4299e1','icon'=>'📅'],
'Visit' => ['color'=>'#ecc94b','icon'=>'🚗'],
'Demo' => ['color'=>'#9f7aea','icon'=>'🖥️'],
'Share Docs' => ['color'=>'#ed8936','icon'=>'📄'],
'To Do' => ['color'=>'#718096','icon'=>'✓'],
];
?>
<?php if (!empty($activity_breakdown)): ?>
<?php foreach ($activity_breakdown as $item): ?>
<?php $cfg = $activityConfig[$item['activity_type']] ?? ['color'=>'#718096','icon'=>'📌']; ?>
<div class="breakdown-item">
<span style="min-width:90px;"><?= $cfg['icon'] ?> <?= esc($item['activity_type']) ?></span>
<div class="breakdown-bar-wrap">
<div class="breakdown-bar" style="width:<?= $item['percentage'] ?>%; background:<?= $cfg['color'] ?>;"></div>
</div>
<strong style="min-width:34px; text-align:right; color:#1a202c;"><?= $item['percentage'] ?>%</strong>
</div>
<?php endforeach; ?>
<?php else: ?>
<div style="text-align:center; color:#aaa; font-size:13px;">No activities found</div>
<?php endif; ?>
</div>
</div>
</div><!-- end .main-grid -->
<!-- ── All Leads Overview ── -->
<div class="card" style="width:100%; box-sizing:border-box;">
<div class="table-header">
<h3 style="font-size:15px; font-weight:700; margin:0;">All Leads Overview</h3>
</div>
<div class="leads-table-wrap">
<table>
<thead>
<tr>
<th>Company</th>
<th>Status</th>
<th>Assigned To</th>
<th style="text-align:center;">Activities</th>
<th style="text-align:center;">Opportunities</th>
</tr>
</thead>
<tbody>
<?php if (!empty($leads_overview)): ?>
<?php foreach ($leads_overview as $lead): ?>
<?php $statusClass = strtolower(str_replace(' ', '-', $lead['status'])); ?>
<tr>
<td><strong><?= esc($lead['company_name']) ?></strong></td>
<td><span class="status-pill status-<?= $statusClass ?>"><?= esc($lead['status']) ?></span></td>
<td><?= esc($lead['assigned_to'] ?? 'Unassigned') ?></td>
<td><div style="text-align:center; font-weight:700;"><?= $lead['activities'] ?></div></td>
<td><div style="text-align:center; font-weight:700;"><?= $lead['opportunities'] ?></div></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="5"><div class="empty-state"><div class="empty-icon">👤</div>No leads found</div></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div><!-- end .dash-container -->
<!-- ══ MODAL ══ -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal-box">
<div class="modal-header">
<div class="modal-ava" id="modalAva"></div>
<div>
<div class="modal-name" id="modalName"></div>
<div class="modal-fy-badge" id="modalFyBadge"></div>
</div>
<button class="modal-close" onclick="closeModal()">&#x2715;</button>
</div>
<div class="modal-summary" id="modalSummary"></div>
<div class="modal-body" id="modalBody"></div>
</div>
</div>
<script>
/* ── Data injected from PHP ── */
const TEAM = <?php echo json_encode($team_achievement ?? []); ?>;
const OPP_DATA = <?php echo json_encode($opp_achievement ?? []); ?>;
/* OPP_DATA shape per userId:
{ total_policies, total_exp_amt, target_amt,
policies: [{ policy_no, issue_date, amount, created_at }] } */
/* ── JS Helpers ── */
const ACT_ICONS = {Call:'📞',Email:'✉️',Meeting:'📅',Visit:'🚗',Demo:'🖥️','Share Docs':'📄','To Do':'✓'};
const ACT_COLORS = {Call:'#4f46e5',Email:'#10b981',Meeting:'#06b6d4',Visit:'#f59e0b',Demo:'#ec4899','Share Docs':'#f97316','To Do':'#64748b'};
const fmt = v => '₹' + (v / 100000).toFixed(1) + 'L';
const fmtFull = v => '₹' + Number(v).toLocaleString('en-IN');
const pct = (a, t) => t > 0 ? Math.min(100, Math.round(a / t * 100)) : 0;
const badge = p => p >= 100 ? ['b-achieved','🏆 Achieved'] : p >= 75 ? ['b-ontrack','🎯 On Track'] : p >= 50 ? ['b-behind','⚡ Behind'] : ['b-atrisk','⚠️ At Risk'];
const progColor= p => p >= 100 ? 'linear-gradient(90deg,#10b981,#34d399)' : p >= 75 ? 'linear-gradient(90deg,#06b6d4,#67e8f9)' : p >= 50 ? 'linear-gradient(90deg,#f97316,#fbbf24)' : 'linear-gradient(90deg,#ef4444,#fca5a5)';
/* ── Date formatters ── */
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
function fmtIssueDate(str) {
// Input: "2026-03-10" or any date string → "Mar 10, 2026"
if (!str) return '—';
const d = new Date(str);
if (isNaN(d)) return str;
return MONTHS[d.getMonth()] + ' ' + String(d.getDate()).padStart(2,'0') + ', ' + d.getFullYear();
}
function fmtCreatedAt(str) {
// Input: "2025-03-04 10:00:00" → "Mar 04, 2025, 10:00 AM"
if (!str) return '—';
const d = new Date(str.replace(' ', 'T'));
if (isNaN(d)) return str;
let h = d.getHours(), mi = d.getMinutes();
const ampm = h >= 12 ? 'PM' : 'AM';
h = h % 12 || 12;
return MONTHS[d.getMonth()] + ' ' + String(d.getDate()).padStart(2,'0') + ', ' + d.getFullYear()
+ ', ' + String(h).padStart(2,'0') + ':' + String(mi).padStart(2,'0') + ' ' + ampm;
}
/* ── Financial Year helpers ── */
function getCurrentFY() {
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth();
const startYear = month >= 3 ? year : year - 1;
return startYear + '-' + String(startYear + 1);
}
function formatFYLabel(fy) {
if (!fy) return '';
const parts = fy.split('-');
if (parts.length === 2) return 'FY ' + parts[0] + '-' + parts[1].slice(-2);
return 'FY ' + fy;
}
/* ── Init ── */
function initFY() {
const select = document.getElementById('financial_year');
const params = new URLSearchParams(window.location.search);
let fy = params.get('fy');
if (!fy) {
fy = getCurrentFY();
let matched = false;
for (let opt of select.options) { if (opt.value === fy) { matched = true; break; } }
if (!matched && select.options.length > 0) fy = select.options[0].value;
}
select.value = fy;
updateFYLabel(fy);
renderBranchSummary();
renderList();
}
function updateFYLabel(fy) {
document.getElementById('fySubLabel').textContent = '' + formatFYLabel(fy);
}
function onFinancialYearChange(select) {
const url = new URL(window.location.href);
url.searchParams.set('fy', select.value);
window.location.href = url.toString();
}
/* ── Branch Summary ── */
function renderBranchSummary() {
if (!TEAM || TEAM.length === 0) return;
const totalAchieved = TEAM.reduce((s, m) => s + (parseFloat(m.achieved_amt) || 0), 0);
const totalTarget = TEAM.reduce((s, m) => s + (parseFloat(m.target_amt) || 0), 0);
const p = pct(totalAchieved, totalTarget);
document.getElementById('branchTotalAmt').innerHTML = fmt(totalAchieved) + ' <span style="font-size:13px;color:#7c8db0;font-weight:600;">/ ' + fmt(totalTarget) + '</span>';
document.getElementById('branchBarFill').style.width = p + '%';
document.getElementById('branchPctLbl').textContent = p + '% of branch target';
}
/* ── Achievement List ── */
function renderList() {
if (!TEAM || TEAM.length === 0) {
document.getElementById('achieveList').innerHTML = '<div class="empty-state"><div class="empty-icon">👤</div>No team achievement data</div>';
return;
}
const sorted = [...TEAM].sort((a, b) => pct(b.achieved_amt, b.target_amt) - pct(a.achieved_amt, a.target_amt));
document.getElementById('achieveList').innerHTML = sorted.map((m, i) => {
const p = pct(m.achieved_amt, m.target_amt);
const rem = Math.max(0, m.target_amt - m.achieved_amt);
const [bc, bt] = badge(p);
const grad = m.grad || 'linear-gradient(135deg,#4f46e5,#818cf8)';
const color = m.color || '#4f46e5';
return '<div class="achieve-row" onclick="openModal(' + m.id + ')">'
+ '<div class="member-cell">'
+ '<div style="font-size:14px;font-weight:800;color:#cbd5e1;min-width:26px;">#' + (i+1) + '</div>'
+ '<div class="m-avatar" style="background:' + grad + ';">' + (m.first_name||'?')[0] + '</div>'
+ '<div><div class="m-name">' + m.first_name + '</div></div>'
+ '</div>'
+ '<div style="padding-right:20px;">'
+ '<div class="prog-row"><span class="prog-pct" style="color:' + color + ';">' + p + '%</span><span class="prog-amts">' + fmt(m.achieved_amt) + ' / ' + fmt(m.target_amt) + '</span></div>'
+ '<div class="prog-bar-wrap"><div class="prog-bar" style="width:' + p + '%;background:' + grad + ';"></div></div>'
+ '</div>'
+ '<div class="num-cell"><div class="num-val">' + fmt(m.achieved_amt) + '</div></div>'
+ '<div class="num-cell"><div class="num-val" style="color:#7c8db0;">' + fmt(m.target_amt) + '</div></div>'
+ '<div class="num-cell"><div class="num-val" style="color:' + (rem > 0 ? '#f97316' : '#10b981') + ';">' + (rem > 0 ? fmt(rem) : '—') + '</div></div>'
+ '<div style="display:flex;justify-content:center;"><span class="badge ' + bc + '">' + bt + '</span></div>'
+ '<div class="num-cell center-col"><div class="num-val">' + m.done_acts + '<span style="color:#cbd5e1;font-size:12px;font-weight:600;">/' + m.total_acts + '</span></div><div class="num-lbl">DONE/TOTAL</div></div>'
+ '<span class="row-arrow"></span>'
+ '</div>';
}).join('');
}
/* ── Modal ── */
function openModal(id) {
const m = TEAM.find(x => x.id == id);
if (!m) return;
const p = pct(m.achieved_amt, m.target_amt);
const rem = Math.max(0, m.target_amt - m.achieved_amt);
const [bc, bt] = badge(p);
const grad = m.grad || 'linear-gradient(135deg,#4f46e5,#818cf8)';
const fySelect = document.getElementById('financial_year');
const currentFY = fySelect ? fySelect.value : getCurrentFY();
const fyLabel = formatFYLabel(currentFY);
document.getElementById('modalAva').style.background = grad;
document.getElementById('modalAva').textContent = (m.first_name || '?')[0];
document.getElementById('modalName').textContent = m.first_name;
document.getElementById('modalFyBadge').textContent = fyLabel;
document.getElementById('modalSummary').innerHTML =
'<div class="ms-box"><div class="ms-num" style="color:#4f46e5;">' + fmt(m.achieved_amt) + '</div><div class="ms-lbl">ACHIEVED</div></div>'
+ '<div class="ms-box"><div class="ms-num">' + fmt(m.target_amt) + '</div><div class="ms-lbl">TARGET</div></div>'
+ '<div class="ms-box"><div class="ms-num" style="color:#f97316;">' + fmt(rem) + '</div><div class="ms-lbl">REMAINING</div></div>'
+ '<div class="ms-box"><div class="ms-num" style="color:' + (p>=75?'#10b981':p>=50?'#f97316':'#ef4444') + ';">' + p + '%</div><div class="ms-lbl">ACHIEVED %</div></div>';
/* ── Overall progress block (shared) ── */
const overallHtml =
'<div class="overall-prog">'
+ '<div class="op-label">OVERALL TARGET ACHIEVEMENT</div>'
+ '<div class="op-row">'
+ '<div><div class="op-achieved">' + fmt(m.achieved_amt) + '</div><div class="op-target">of ' + fmt(m.target_amt) + ' target</div></div>'
+ '<div class="op-pct">' + p + '%</div>'
+ '</div>'
+ '<div class="op-bar-wrap"><div class="op-bar-fill" style="width:' + p + '%;"></div></div>'
+ '<div class="op-foot"><span>Remaining: <strong style="color:#fbbf24;">' + fmt(rem) + '</strong></span>'
+ '<span class="badge ' + bc + '" style="background:rgba(255,255,255,.1);color:#e2e8f0;">' + bt + '</span>'
+ '</div>'
+ '</div>';
/* ════════════════════════════
TAB 1 — Quarter-wise splits
════════════════════════════ */
const splits = m.splits || [];
const splitRows = splits.map(function(s) {
const sp = pct(s.achieved, s.target);
const srem = Math.max(0, s.target - s.achieved);
const [sbc, sbt] = badge(sp);
/* % cell: number on top, badge below (badge hidden when 0%) */
const pctColor = sp >= 75 ? '#10b981' : sp >= 50 ? '#f97316' : '#ef4444';
const pctCell = '<div style="font-weight:800;font-size:15px;color:' + pctColor + ';">' + sp + '%</div>'
+ (sp > 0 ? '<div style="margin-top:3px;"><span class="split-pct-badge ' + sbc + '" style="font-size:10px;padding:2px 8px;">' + sbt + '</span></div>' : '');
/* Activities cell: total bold on top, done in muted smaller text below */
const actsCell = '<div style="font-weight:800;font-size:15px;">' + s.acts + '</div>'
+ '<div style="font-size:11px;color:#94a3b8;font-weight:600;margin-top:2px;">' + s.done + ' done</div>';
return '<tr>'
+ '<td><div class="split-name">' + s.name + '</div><div class="split-dates">📅 ' + s.start + ' → ' + s.end + '</div></td>'
+ '<td><div style="font-weight:800;">' + fmt(s.achieved) + '</div><div class="mini-prog-wrap"><div class="mini-prog-bar" style="width:' + sp + '%;background:' + progColor(sp) + ';"></div></div></td>'
+ '<td style="font-weight:700;color:#7c8db0;">' + fmt(s.target) + '</td>'
+ '<td style="font-weight:700;color:#f97316;">' + (srem > 0 ? fmt(srem) : '—') + '</td>'
+ '<td>' + pctCell + '</td>'
+ '<td>' + actsCell + '</td>'
+ '<td style="font-weight:700;">' + s.leads + '</td>'
+ '</tr>';
}).join('');
const actChips = Object.entries(m.activities || {}).map(function(entry) {
const k = entry[0], v = entry[1];
return '<div class="act-chip">'
+ '<div class="act-dot" style="background:' + (ACT_COLORS[k]||'#64748b') + ';"></div>'
+ (ACT_ICONS[k]||'📌') + ' ' + k + ': <strong>' + v + '</strong>'
+ '</div>';
}).join('');
const tab1Html = overallHtml
+ '<div class="split-section-title">📊 Quarter-wise Achievement Breakdown</div>'
+ '<div style="overflow-x:auto;margin-bottom:20px;">'
+ '<table class="split-table">'
+ '<thead><tr>'
+ '<th>Quarter</th><th>Achieved</th><th>Target</th><th>Remaining</th>'
+ '<th style="text-align:center;">Achievement %</th>'
+ '<th style="text-align:center;">Activities</th>'
+ '<th style="text-align:center;">Leads</th>'
+ '</tr></thead>'
+ '<tbody>' + (splitRows || '<tr><td colspan="7" style="text-align:center;color:#aaa;padding:20px;">No split data available</td></tr>') + '</tbody>'
+ '</table>'
+ '</div>'
+ '<div class="split-section-title">⚡ Activity Breakdown</div>'
+ '<div class="act-chips">' + (actChips || '<span style="color:#aaa;font-size:13px;">No activity data</span>') + '</div>';
/* ════════════════════════════
TAB 2 — Opportunities
════════════════════════════ */
const opp = OPP_DATA[id] || null;
let tab2Html = '';
if (opp) {
const oppPct = pct(opp.total_exp_amt, opp.target_amt);
const [obc, obt] = badge(oppPct);
const oppPctColor = oppPct >= 75 ? '#10b981' : oppPct >= 50 ? '#f97316' : '#ef4444';
/* Summary cards */
const summaryCards =
'<div class="opp-summary-cards">'
+ '<div class="opp-card"><div class="opp-card-val" style="color:#4f46e5;">' + opp.total_policies + '</div><div class="opp-card-lbl">TOTAL POLICIES WON</div></div>'
+ '<div class="opp-card"><div class="opp-card-val" style="color:#10b981;">' + fmtFull(opp.total_exp_amt) + '</div><div class="opp-card-lbl">TOTAL EXP AMOUNT</div></div>'
+ '<div class="opp-card"><div class="opp-card-val" style="color:' + oppPctColor + ';">' + oppPct + '%</div><div class="opp-card-lbl">VS TARGET</div></div>'
+ '</div>';
/* Dark progress bar */
const oppProgress =
'<div style="background:linear-gradient(135deg,#0f172a,#1e293b);border-radius:14px;padding:18px 20px;color:#fff;margin-bottom:20px;">'
+ '<div class="op-label">OPPORTUNITIES ACHIEVEMENT</div>'
+ '<div class="op-row">'
+ '<div><div class="op-achieved">' + fmtFull(opp.total_exp_amt) + '</div><div class="op-target">of ' + fmtFull(opp.target_amt) + ' target</div></div>'
+ '<div class="op-pct">' + oppPct + '%</div>'
+ '</div>'
+ '<div class="op-bar-wrap"><div class="op-bar-fill" style="width:' + oppPct + '%;"></div></div>'
+ '<div class="op-foot"><span>' + opp.total_policies + ' policies won</span>'
+ '<span class="badge ' + obc + '" style="background:rgba(255,255,255,.1);color:#e2e8f0;">' + obt + '</span>'
+ '</div>'
+ '</div>';
/* Policy rows — NO quarter grouping */
const policies = opp.policies || [];
const policyRows = policies.map(function(pol) {
return '<tr>'
+ '<td style="font-weight:700;color:#0f172a;">' + (pol.policy_no || '—') + '</td>'
+ '<td style="font-weight:600;color:#334155;">' + fmtIssueDate(pol.issue_date) + '</td>'
+ '<td style="font-weight:800;color:#0f172a;">' + fmtFull(pol.amount || 0) + '</td>'
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(pol.created_at) + '</td>'
+ '</tr>';
}).join('');
tab2Html = "";
// tab2Html = summaryCards
// + oppProgress
// + '<div class="split-section-title">📋 Policy Details</div>'
// + '<div style="overflow-x:auto;">'
// + '<table class="split-table">'
// + '<thead><tr>'
// + '<th>Policy No.</th>'
// + '<th>Issue Date</th>'
// + '<th>Amount</th>'
// + '<th>Created Date</th>'
// + '</tr></thead>'
// + '<tbody>' + (policyRows || '<tr><td colspan="4" style="text-align:center;color:#aaa;padding:24px;">No policies found</td></tr>') + '</tbody>'
// + '</table>'
// + '</div>';
/* ── Won Leads Table (Table 2) ── */
const wonLeads = opp.won_leads || [];
const wonRows = wonLeads.map(function(l) {
const typeBadgeClass = l.lead_type === 'EB'
? 'style="background:#eff6ff;color:#2563eb;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"'
: 'style="background:#f0fdf4;color:#16a34a;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:700;"';
return '<tr>'
+ '<td style="font-weight:700;color:#0f172a;">' + (l.company || '—') + '</td>'
+ '<td><span ' + typeBadgeClass + '>' + (l.lead_type || '—') + '</span></td>'
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(l.created_at) + '</td>'
+ '</tr>';
}).join('');
tab2Html +=
'<div class="split-section-title" style="margin-top:24px;"> Opportunities </div>'
+ '<div style="overflow-x:auto;">'
+ '<table class="split-table">'
+ '<thead><tr>'
+ '<th>Company</th>'
+ '<th>Opportunity Type</th>'
+ '<th>Date</th>'
+ '</tr></thead>'
+ '<tbody>' + (wonRows || '<tr><td colspan="3" style="text-align:center;color:#aaa;padding:24px;">No leads found</td></tr>') + '</tbody>'
+ '</table>'
+ '</div>';
} else {
tab2Html = '<div class="empty-state"><div class="empty-icon">📋</div>No opportunities data available</div>';
}
/* ── Inject tabs ── */
document.getElementById('modalBody').innerHTML =
'<div class="modal-tabs">'
+ '<button class="modal-tab active" onclick="switchTab(0,this)">📊 ' + fyLabel + ' Split-wise Achievement</button>'
+ '<button class="modal-tab" onclick="switchTab(1,this)">🏆 Opportunities Achievement</button>'
+ '</div>'
+ '<div class="modal-tab-panel active" id="tabPanel0">' + tab1Html + '</div>'
+ '<div class="modal-tab-panel" id="tabPanel1">' + tab2Html + '</div>';
document.getElementById('modalOverlay').classList.add('open');
document.body.style.overflow = 'hidden';
}
function switchTab(idx, btn) {
document.querySelectorAll('.modal-tab').forEach(function(t) { t.classList.remove('active'); });
document.querySelectorAll('.modal-tab-panel').forEach(function(p) { p.classList.remove('active'); });
btn.classList.add('active');
document.getElementById('tabPanel' + idx).classList.add('active');
}
function closeModal() {
document.getElementById('modalOverlay').classList.remove('open');
document.body.style.overflow = '';
}
document.getElementById('modalOverlay').addEventListener('click', function(e) { if (e.target === e.currentTarget) closeModal(); });
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeModal(); });
document.addEventListener('DOMContentLoaded', initFY);
</script>