Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
velz 2026-06-04 09:21:19 +05:30
commit c22eebc96b
5 changed files with 245 additions and 52 deletions

View File

@ -1032,8 +1032,9 @@ class EmpDataServiceController extends BaseController
return false;
}
//for cd tranction and cd master data is empty check
if(!empty($inceptionData) && $inceptionData != null){
if(!empty($inceptionData) && $inceptionData != null || !empty($additionData) && $additionData != null || !empty($dependentAdditionData) && $dependentAdditionData != null){
$policy_details = $this->clientPolicyModel->where('id', $export_data['client_policy_id'])->first();
@ -1065,6 +1066,14 @@ class EmpDataServiceController extends BaseController
$totals = $totals + $item->total;
}
foreach ($additionData as $item) {
$totals = $totals + $item->total;
}
foreach ($dependentAdditionData as $item) {
$totals = $totals + $item->total;
}
$totals = round($totals, 2);
@ -1127,8 +1136,7 @@ class EmpDataServiceController extends BaseController
AND `insurer_excel_export_template`.`type_name` = :type_name:
LIMIT 1";
$binds = ['client_policy_id' => (int)$export_data['client_policy_id'],
'type_name' => $export_data['actions']];
$binds = ['client_policy_id' => (int)$export_data['client_policy_id'],'type_name' => $export_data['actions']];
$query = db_connect()->query($sql,$binds);
$template_json = $query->getRowArray();

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

@ -14,7 +14,8 @@
text-overflow: ellipsis;
}
.select2-hidden-accessible + .select2-container .select2-dropdown {
/* Hide dropdown list only for the insurer copy select (search-only UX) */
#insurer.select2-hidden-accessible + .select2-container .select2-dropdown {
display: none !important;
}
@ -53,6 +54,14 @@
z-index: 1060;
}
#template_modal .select2-container {
z-index: 1061;
}
#template_modal .select2-dropdown {
z-index: 1062;
}
#template_modal.show {
display: flex !important;
align-items: center;
@ -276,6 +285,44 @@
<script>
var insurerTemplateDataTable = null;
var templateDebugPrefix = '[InsurerTemplate]';
var insurerDbColumnOptions = <?= json_encode($db_column_name ?? [], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?>;
function buildDbColumnSelectOptions() {
var html = '<option value="" selected>Select</option>';
if (!insurerDbColumnOptions || typeof insurerDbColumnOptions !== 'object') {
return html;
}
Object.keys(insurerDbColumnOptions).forEach(function (label) {
var value = insurerDbColumnOptions[label];
html += '<option value="' + String(value).replace(/"/g, '&quot;') + '">' + String(label).replace(/</g, '&lt;') + '</option>';
});
return html;
}
function initDbColumnSelect2($select) {
if (!$select || !$select.length || typeof $.fn.select2 !== 'function') {
return;
}
if ($select.hasClass('select2-hidden-accessible')) {
$select.select2('destroy');
}
$select.select2({
placeholder: 'Select',
allowClear: true,
width: '100%',
dropdownParent: $('#template_modal')
});
}
function destroyDbColumnSelect2InContainer(container) {
var $container = container ? $(container) : $('#dynamic-form-container');
$container.find('.db-column-name-select').each(function () {
var $el = $(this);
if ($el.hasClass('select2-hidden-accessible')) {
$el.select2('destroy');
}
});
}
function showTemplateModal() {
var $modal = $('#template_modal');
@ -662,6 +709,7 @@
input.preventDefault();
}
$('#myCenterModalLabel').text('Create Template');
destroyDbColumnSelect2InContainer();
$('#dynamic-form-container').empty();
addHTMLInput();
showTemplateModal();
@ -672,6 +720,10 @@
const container = document.getElementById('dynamic-form-container');
const newRow = document.createElement('div');
newRow.className = 'form-row dynamic-form-row';
const disableDbSelect = data !== null && data !== undefined && data !== '' && data.default_value != "" && data.default_value != null;
const headerValue = data !== null && data !== undefined && data !== '' ? (data.column_name || '') : '';
const defaultValue = data != null && data != undefined && data != '' && data.default_value != "" && data.default_value != null ? data.default_value : '';
newRow.innerHTML = `
<div class="mt-3 p-3" style="width: 100%; background: #F7F7F7;
border-radius: 30px !important;
@ -681,24 +733,19 @@
">
<div class="form-group col-md-3">
<label for="db_column_name">DataBase Column Name<span class="text-danger"></span></label>
<select class="form-control db-column-name-select dbColumn" name="db_column_name[]" onchange="checkForDuplicates(this); handleDbColumnChange(this)" ${data !== null && data !== undefined && data !== '' && data.default_value != "" && data.default_value != null ? 'disabled' : ''}>
<option value="" selected >Select</option>
<?php if (!empty($db_column_name)){ ?>
<?php foreach ($db_column_name as $key => $value) { ?>
<option value="<?= $value ?>"><?= $key ?></option>
<?php } ?>
<?php } ?>
<select class="form-control db-column-name-select" name="db_column_name[]" onchange="checkForDuplicates(this); handleDbColumnChange(this)"${disableDbSelect ? ' disabled' : ''}>
${buildDbColumnSelectOptions()}
</select>
</div>
<div class="form-group col-md-4">
<label for="excel_column_name">Header Name<span class="text-danger">*</span></label>
<input id="excel_column_name" value="${data !== null && data !== undefined && data !== '' ? data.column_name : ''}" class="form-control" type="text" name="excel_column_name[]" placeholder="Excel Header Column Name">
<input id="excel_column_name" value="${headerValue}" class="form-control" type="text" name="excel_column_name[]" placeholder="Excel Header Column Name">
</div>
<div class="form-group col-md-3">
<label for="default_value">Default Value<span class="text-danger"></span></label>
<input id="default_value" value="${data != null && data != undefined && data != '' && data.default_value != "" && data.default_value != null ? data.default_value : ''}" class="form-control" type="text" name="default_value[]" placeholder="Excel Default value">
<input id="default_value" value="${defaultValue}" class="form-control" type="text" name="default_value[]" placeholder="Excel Default value">
</div>
<div class="form-group col-md-2" style="position: relative;left: 28px;">
@ -708,13 +755,14 @@
</div>
`;
container.appendChild(newRow);
var $dbSelect = $(newRow).find('.db-column-name-select');
if (data != null && data.db_column_name != undefined) {
const selectElement = newRow.querySelector('.db-column-name-select');
selectElement.value = data.db_column_name;
$('.dbColumn').select2();
$dbSelect.val(data.db_column_name);
}
initDbColumnSelect2($dbSelect);
if (data != null && data.db_column_name != undefined) {
$dbSelect.trigger('change');
}
$('.dbColumn').select2();
}
// function removeHTMLInput(element)
@ -731,6 +779,12 @@
if (rows.length > 1) {
const row = element.closest('.dynamic-form-row');
$(row).find('.db-column-name-select').each(function () {
var $el = $(this);
if ($el.hasClass('select2-hidden-accessible')) {
$el.select2('destroy');
}
});
row.remove();
}
}
@ -813,6 +867,7 @@
var data = JSON.parse(res.data.jsoncolumns)
// console.log(JSON.parse(res.data.jsoncolumns));
destroyDbColumnSelect2InContainer();
$('#dynamic-form-container').empty();
$.each(data, function(index, item) {
@ -855,6 +910,7 @@
$('.close').click(function()
{
destroyDbColumnSelect2InContainer();
$('#dynamic-form-container').empty();
$('#policy_type').val('');
$('#event').val('').change();
@ -926,9 +982,9 @@
let $dbSelect = $row.find('select[name="db_column_name[]"]');
if ($defaultInput.val().trim() !== '') {
$dbSelect.val('').trigger('change').prop('disabled', true);
$dbSelect.val(null).trigger('change').prop('disabled', true);
} else {
$dbSelect.prop('disabled', false);
$dbSelect.prop('disabled', false).trigger('change');
}
});

View File

@ -1256,11 +1256,20 @@
// Start the interval
function startInterval() {
if (!submitInterval) {
messageShown = false; // Reset message flag
messageShown = false;
submitInterval = setInterval(function() {
checkCDBalance();
}, 2000);
console.log("Checking CD balance started...");
// Stop after 15 seconds
setTimeout(function() {
clearInterval(submitInterval);
submitInterval = null;
console.log("Checking CD balance stopped after 15 seconds.");
}, 15000);
}
}

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(); });