diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php index 659299cd..edf64242 100644 --- a/app/Controllers/SalesController.php +++ b/app/Controllers/SalesController.php @@ -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, ]; } diff --git a/app/Views/sales/branch_level_dashboard_view.php b/app/Views/sales/branch_level_dashboard_view.php index eba15043..09be0731 100644 --- a/app/Views/sales/branch_level_dashboard_view.php +++ b/app/Views/sales/branch_level_dashboard_view.php @@ -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 = ; const OPP_DATA = ; /* 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, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +function updateOppTableHeader(showWon) { + const headRow = document.getElementById('oppTableHead'); + if (!headRow) return; + + if (showWon) { + headRow.innerHTML = + '
| Company | ' + 'Opportunity Type | ' + 'Date | ' + '
|---|---|---|
| No leads found | ||