Sales tracker Changes

This commit is contained in:
sanjeev.p 2026-06-03 17:40:58 +05:30
parent 11b47b2dff
commit 67d89e1fd5
2 changed files with 151 additions and 31 deletions

View File

@ -2028,6 +2028,35 @@ private function buildOppAchievement($db, array $sales_manager_ids, $branchId, s
ORDER BY l.created_at DESC
", [$uid, $fyStart, $fyEnd])->getResultArray();
// ── Lost Leads for this user in FY (Opportunities tab — toggle off) ──
$lossLeads = $db->query("
SELECT
l.id AS opportunities_id,
l.actual_lead_id,
COALESCE(l.lead_form_type, 1) AS lead_form_type_id,
l.lead_type AS lead_type_id,
sal.company_name AS company,
l.client_name AS client_name,
CASE WHEN COALESCE(l.lead_form_type, 1) = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_form_type,
CASE
WHEN l.lead_type = 1 THEN 'Fresh'
WHEN l.lead_type = 2 THEN 'Renewal'
WHEN l.lead_type = 3 THEN 'Roll Over'
ELSE ''
END AS lead_type,
l.created_at AS created_at,
l.status,
l.lost_reason AS lost_reason
FROM leads l
INNER JOIN sales_actual_leads sal
ON sal.lead_id = l.actual_lead_id
WHERE l.status = 'lost'
AND sal.assigned_to = ?
AND l.created_at >= ?
AND l.created_at <= ?
ORDER BY l.created_at DESC
", [$uid, $fyStart, $fyEnd])->getResultArray();
// -- NOTE: AND TRIM(sal.company_name) = TRIM(l.client_name)
$result[$uid] = [
'total_policies' => $totalPolicies,
@ -2035,6 +2064,7 @@ private function buildOppAchievement($db, array $sales_manager_ids, $branchId, s
'target_amt' => $targetAmt,
'policies' => $policies,
'won_leads' => $wonLeads, // for Table 2 in modal Tab 2
'loss_leads' => $lossLeads,
];
}

View File

@ -230,6 +230,17 @@
.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; }
.opp-section-head { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 12px; gap: 12px; flex-wrap: wrap; }
.opp-section-head .split-section-title { margin: 0; }
.opp-toggle-wrap { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.opp-toggle-lbl { font-size: 12px; font-weight: 700; color: #94a3b8; transition: color .2s; }
.opp-toggle-lbl.active { color: #0f172a; }
.opp-toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; }
.opp-toggle-switch input { opacity: 0; width: 0; height: 0; }
.opp-toggle-slider { position: absolute; cursor: pointer; inset: 0; background: #cbd5e1; border-radius: 99px; transition: background .2s; }
.opp-toggle-slider::before { content: ""; position: absolute; height: 18px; width: 18px; left: 3px; bottom: 3px; background: #fff; border-radius: 50%; transition: transform .2s; box-shadow: 0 1px 3px rgba(15,23,42,0.15); }
.opp-toggle-switch input:checked + .opp-toggle-slider { background: #4f46e5; }
.opp-toggle-switch input:checked + .opp-toggle-slider::before { transform: translateX(20px); }
@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); } .target-edit-panel { grid-template-columns: 1fr; } .opp-summary-cards { grid-template-columns: 1fr 1fr; } }
@ -502,7 +513,9 @@ 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 }] } */
policies: [{ policy_no, issue_date, amount, created_at }],
won_leads: [...], loss_leads: [...] } */
let currentModalOppUserId = null;
/* ── JS Helpers ── */
const ACT_ICONS = {Call:'📞',Email:'✉️',Meeting:'📅',Visit:'🚗',Demo:'🖥️','Share Docs':'📄','To Do':'✓'};
@ -545,6 +558,89 @@ function redirectToOpportunity(leadTypeId, actualLeadId, opportunityId) {
+ encodeURIComponent(opportunityId);
}
function escOppCell(text) {
return String(text ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function updateOppTableHeader(showWon) {
const headRow = document.getElementById('oppTableHead');
if (!headRow) return;
if (showWon) {
headRow.innerHTML =
'<th>Company</th>'
+ '<th>Opportunity Type</th>'
+ '<th>Date</th>';
} else {
headRow.innerHTML =
'<th>Company</th>'
+ '<th>Opportunity Type</th>'
+ '<th>Lost Reason</th>'
+ '<th>Date</th>';
}
}
function buildOpportunityTableRows(leads, showWon) {
return (leads || []).map(function(l) {
const opportunityType = [
l.lead_form_type || '',
].filter(Boolean).join('').toUpperCase();
const typeBadgeClass = l.lead_form_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;"';
const canRedirect = l.lead_form_type_id && l.actual_lead_id && l.opportunities_id;
const rowAttrs = canRedirect
? ' class="won-opportunity-row" title="Open opportunity" onclick=\'redirectToOpportunity('
+ JSON.stringify(String(l.lead_form_type_id)) + ','
+ JSON.stringify(String(l.actual_lead_id)) + ','
+ JSON.stringify(String(l.opportunities_id)) + ')\''
: '';
const lostReasonCell = showWon ? '' :
'<td style="font-size:12px;color:#64748b;max-width:220px;white-space:normal;">'
+ escOppCell((l.lost_reason || '').trim() || '—')
+ '</td>';
return '<tr' + rowAttrs + '>'
+ '<td style="font-weight:700;color:#0f172a;">' + escOppCell(l.company || '—') + '</td>'
+ '<td><span ' + typeBadgeClass + '>' + (opportunityType || '—') + '</span></td>'
+ lostReasonCell
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(l.created_at) + '</td>'
+ '</tr>';
}).join('');
}
function updateOppToggleLabels(showWon) {
const lostLbl = document.getElementById('oppToggleLblLost');
const wonLbl = document.getElementById('oppToggleLblWon');
if (lostLbl) lostLbl.classList.toggle('active', !showWon);
if (wonLbl) wonLbl.classList.toggle('active', showWon);
}
function renderOpportunitiesTable(userId, showWon) {
const opp = OPP_DATA[userId];
const tbody = document.getElementById('oppTableBody');
if (!tbody || !opp) return;
const leads = showWon ? (opp.won_leads || []) : (opp.loss_leads || []);
const colSpan = showWon ? 3 : 4;
updateOppTableHeader(showWon);
const rows = buildOpportunityTableRows(leads, showWon);
const emptyMsg = showWon ? 'No won opportunities found' : 'No lost opportunities found';
tbody.innerHTML = rows || '<tr><td colspan="' + colSpan + '" style="text-align:center;color:#aaa;padding:24px;">' + emptyMsg + '</td></tr>';
updateOppToggleLabels(showWon);
}
function onOppStatusToggle(checked) {
if (currentModalOppUserId) {
renderOpportunitiesTable(currentModalOppUserId, checked);
}
}
function goSalesPage(page, status, memberId) {
const url = new URL(page === 'activities' ? SALES_ACTIVITIES_URL : SALES_LEADS_URL, window.location.origin);
url.searchParams.set('fy', getSelectedDashboardFY());
@ -939,48 +1035,33 @@ function openModal(id) {
// + '</table>'
// + '</div>';
/* ── Won Leads Table (Table 2) ── */
const wonLeads = opp.won_leads || [];
const wonRows = wonLeads.map(function(l) {
// const opportunityType = [
// l.lead_form_type || '',
// l.lead_type || ''
// ].filter(Boolean).join('/').toUpperCase();
const opportunityType = [
l.lead_form_type || '',
].filter(Boolean).join('').toUpperCase();
const typeBadgeClass = l.lead_form_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;"';
const canRedirect = l.lead_form_type_id && l.actual_lead_id && l.opportunities_id;
const rowAttrs = canRedirect
? ' class="won-opportunity-row" title="Open opportunity" onclick=\'redirectToOpportunity('
+ JSON.stringify(String(l.lead_form_type_id)) + ','
+ JSON.stringify(String(l.actual_lead_id)) + ','
+ JSON.stringify(String(l.opportunities_id)) + ')\''
: '';
return '<tr' + rowAttrs + '>'
+ '<td style="font-weight:700;color:#0f172a;">' + (l.company || '—') + '</td>'
+ '<td><span ' + typeBadgeClass + '>' + (opportunityType || '—') + '</span></td>'
+ '<td style="font-size:12px;color:#64748b;">' + fmtCreatedAt(l.created_at) + '</td>'
+ '</tr>';
}).join('');
currentModalOppUserId = id;
tab2Html +=
'<div class="split-section-title" style="margin-top:24px;"> Opportunities </div>'
'<div class="opp-section-head">'
+ '<div class="split-section-title">Opportunities</div>'
+ '<div class="opp-toggle-wrap">'
+ '<span class="opp-toggle-lbl" id="oppToggleLblLost">Lost</span>'
+ '<label class="opp-toggle-switch" title="Toggle between won and lost opportunities">'
+ '<input type="checkbox" id="oppStatusToggle" checked onchange="onOppStatusToggle(this.checked)">'
+ '<span class="opp-toggle-slider"></span>'
+ '</label>'
+ '<span class="opp-toggle-lbl active" id="oppToggleLblWon">Won</span>'
+ '</div>'
+ '</div>'
+ '<div style="overflow-x:auto;">'
+ '<table class="split-table">'
+ '<thead><tr>'
+ '<thead><tr id="oppTableHead">'
+ '<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>'
+ '<tbody id="oppTableBody"></tbody>'
+ '</table>'
+ '</div>';
} else {
currentModalOppUserId = null;
tab2Html = '<div class="empty-state"><div class="empty-icon">📋</div>No opportunities data available</div>';
}
@ -996,6 +1077,14 @@ function openModal(id) {
document.getElementById('modalOverlay').classList.add('open');
document.body.style.overflow = 'hidden';
if (currentModalOppUserId && OPP_DATA[currentModalOppUserId]) {
const toggle = document.getElementById('oppStatusToggle');
if (toggle) toggle.checked = true;
renderOpportunitiesTable(currentModalOppUserId, true);
} else {
currentModalOppUserId = null;
}
}
function switchTab(idx, btn) {
@ -1007,6 +1096,7 @@ function switchTab(idx, btn) {
function closeModal() {
document.getElementById('modalOverlay').classList.remove('open');
document.body.style.overflow = '';
currentModalOppUserId = null;
}
document.getElementById('modalOverlay').addEventListener('click', function(e) { if (e.target === e.currentTarget) closeModal(); });