+
@@ -213,7 +213,7 @@
@@ -683,7 +683,7 @@ async function fetchActivities(isLoadMore = false) { ${activityIcons[a.activity_type]} ${a.activity_type} - ${a.status} + ${capitalize(a.status)}
+
-
@@ -408,7 +408,7 @@
Lead Detail
+Lead Detail
-
Select Opportunity Type
+Select Opportunity Type
@@ -683,7 +683,7 @@ async function fetchActivities(isLoadMore = false) { ${activityIcons[a.activity_type]} ${a.activity_type} - ${a.status} + ${capitalize(a.status)}
@@ -940,6 +940,11 @@ function openOpportunityModal() {
openModal('opportunityModal');
}
+function capitalize(str) {
+ if (!str) return '';
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
+}
+
function switchTab(tabName) {
//Default Activity Tab how to resset here
// 1. Hide all tab content
diff --git a/app/Views/sales/tracker_view.php b/app/Views/sales/tracker_view.php
index 2f56569d..bb50e05d 100644
--- a/app/Views/sales/tracker_view.php
+++ b/app/Views/sales/tracker_view.php
@@ -145,7 +145,7 @@
-->
- Prospects
Not a Prospects
+
@@ -196,10 +196,43 @@
-
@@ -470,7 +503,7 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
@@ -207,7 +240,7 @@
-
+
@@ -248,7 +281,7 @@
-
@@ -435,7 +468,7 @@
Lead Detail
+Lead Detail
-
Select Opportunity Type
+Select Opportunity Type
@@ -470,7 +503,7 @@
-
+
Edit Lead
@@ -481,10 +514,47 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
@@ -492,7 +562,7 @@
-
+
@@ -553,28 +623,44 @@
+
+
+
+
-
+
+
@@ -713,13 +799,22 @@ function closeModal(id) {
document.getElementById('btn_add_opportunity').style.display = 'none';
}
+ // if (id === 'editLeadModal') {
+ // // const Eform = document.getElementById('editLeadForm');
+ // // if (!Eform) return; // safety check
+ // // Eform.reset();
+ // form.querySelectorAll('[name="status"] option')
+ // .forEach(opt => opt.hidden = false);
+ // form.querySelector('[name="status"]').value = 'New';
+ // }
+ // Replace both add and edit company cleanup blocks with:
+ if (id === 'addLeadModal') {
+ _resetCompanyUI('add');
+ }
if (id === 'editLeadModal') {
- // const Eform = document.getElementById('editLeadForm');
- // if (!Eform) return; // safety check
- // Eform.reset();
- form.querySelectorAll('[name="status"] option')
- .forEach(opt => opt.hidden = false);
+ form.querySelectorAll('[name="status"] option').forEach(opt => opt.hidden = false);
form.querySelector('[name="status"]').value = 'New';
+ _resetCompanyUI('edit');
}
}
}
@@ -730,6 +825,32 @@ function selectType(val, el) {
selectedType = val;
}
+function resetContactForm() {
+ document.getElementById('editing_contact_id').value = '';
+ document.getElementById('contact_name').value = '';
+ document.getElementById('contact_mobile').value = '';
+ document.getElementById('contact_designation').value = '';
+ document.getElementById('contact_is_primary').checked = false;
+ document.getElementById('btnSaveContact').innerHTML = "โ";
+ document.getElementById('btnSaveContact').title = "Save";
+ document.getElementById('btnSaveContact').style.background = "#02a8b5";
+}
+// Helper โ add this once anywhere in your JS
+function _resetCompanyUI(mode) {
+ const ctx = COMPANY_CTX[mode];
+ const state = companyState[mode];
+ state.isExisting = false;
+ state.selectedValue = '';
+ document.getElementById(ctx.clientId).value = '';
+ document.getElementById(ctx.shortNameHidden).value = ''; // โ only this
+ document.getElementById(ctx.selected).style.display = 'none';
+ document.getElementById(ctx.searchInput).style.display = '';
+ document.getElementById(ctx.searchInput).value = '';
+ document.getElementById(ctx.dropdown).style.display = 'none';
+ const b = document.getElementById(ctx.badge);
+ if (b) { b.textContent = ''; b.style.display = 'none'; }
+}
+
function selectFollowUpActivityType(val, el) {
document.querySelectorAll('.f_activity_type').forEach(b => b.classList.remove('active'));
@@ -1143,6 +1264,10 @@ document.getElementById('addLeadForm').onsubmit = async (e) => {
// let phoneRegex = /^\+?[0-9\s]{0,10}$/; // Phone regex (+, numbers, spaces allowed, 10-20 length)
let phoneRegex = /^\d{10}$/;
+ let addState = companyState['add']; // use 'edit' in editLeadForm
+ let modeState = companyState['add']; // โ change to 'edit' for edit form
+
+
// 1. HELPER FUNCTION: Shows Toastr and focuses the specific field
function showError(message, fieldName) {
toastr.warning(message, 'Validation Error');
@@ -1151,6 +1276,13 @@ document.getElementById('addLeadForm').onsubmit = async (e) => {
}, 100);
}
+ if (!modeState.selectedValue && !data.client_id) {
+ return showError('Please select a company from the dropdown or click "Create" to add a new one.', 'company_name');
+ }
+ if (!companyState['add'].selectedValue) {
+ return showError('Please select or create a company from the dropdown.', 'company_name');
+ }
+
// --- VALIDATION CHECKS (Button is still normal here) ---
if (!companyName && !email && !phone && !data.assigned_to) {
toastr.warning('Please fill in all the required fields.');
@@ -1256,6 +1388,9 @@ document.getElementById('editLeadForm').onsubmit = async (e) => {
// Just grab the button variables first, DO NOT disable yet
const submitBtn = e.target.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.innerText;
+ let addState = companyState['edit']; // use 'edit' in editLeadForm
+ let modeState = companyState['edit']; // โ change to 'edit' for edit form
+
const data = Object.fromEntries(new FormData(e.target).entries());
console.log("Lead :", data);
@@ -1281,6 +1416,14 @@ document.getElementById('editLeadForm').onsubmit = async (e) => {
}
// --- VALIDATION CHECKS (Button is still normal here) ---
+ if (!modeState.selectedValue && !data.client_id) {
+ return showError('Please select a company from the dropdown or click "Create" to add a new one.', 'company_name');
+ }
+
+ if (!companyState['edit'].selectedValue) {
+ return showError('Please select or create a company from the dropdown.', 'company_name');
+ }
+
if (!companyName && !email && !phone && !data.assigned_to) {
toastr.warning('Please fill in all the required fields.');
return;
@@ -1343,7 +1486,9 @@ document.getElementById('editLeadForm').onsubmit = async (e) => {
$('#contactPersonsList').find('.contact-card').remove();
$('#noContactPersonsData').show();
document.getElementById('editing_contact_id').value = '';
- document.getElementById('btnSaveContact').innerHTML = "โ Save";
+ document.getElementById('btnSaveContact').innerHTML = "โ";
+ document.getElementById('btnSaveContact').title = "Save";
+ document.getElementById('btnSaveContact').style.background = "#02a8b5";
} else {
let err = await res.json();
if (res.status === 400) {
@@ -1688,8 +1833,11 @@ document.getElementById('do_follow').addEventListener('change', function () {
async function openEditLeadModal(id) {
+
// 1. Reset UI State
document.getElementById('hidden_lead_id').value = id;
+ // โ
ADD THIS โ reset contact form state every time modal opens
+ resetContactForm();
const container = $('#contactPersonsList');
// Remove only previous contact cards, keep the NoData span for now
container.find('.contact-card').remove();
@@ -1706,6 +1854,22 @@ async function openEditLeadModal(id) {
// 3. Populate Form Fields
const form = document.getElementById('editLeadForm');
+ // Populate company for edit modal
+ _resetCompanyUI('edit');
+ if (lead.client_id) {
+ selectExistingCompany({
+ id: lead.client_id,
+ client_name: lead.company_name || '',
+ short_name: lead.short_name || '',
+ email: '', // don't overwrite existing email
+ phone: '', // don't overwrite existing phone
+ }, 'edit');
+ } else {
+ // No linked client โ just show the typed name, allow search
+ companyState['edit'].selectedValue = lead.company_name || '';
+ const inp = document.getElementById('editCompanySearchInput');
+ inp.value = lead.company_name || '';
+ }
// Mapping fields carefully
form.querySelector('[name="company_name"]').value = lead.company_name || '';
@@ -1786,9 +1950,9 @@ document.getElementById('contactPersonsList').onclick = async (e) => {
document.getElementById('contact_is_primary').checked = contactData.is_primary == 1;
// Change Button UI
- const saveBtn = document.getElementById('btnSaveContact');
- saveBtn.innerHTML = "Update";
-
+ document.getElementById('btnSaveContact').innerHTML = "โ๏ธ";
+ document.getElementById('btnSaveContact').title = "Update";
+ document.getElementById('btnSaveContact').style.background = "#02a8b5";
document.getElementById('contact_name').focus();
}
@@ -1841,14 +2005,8 @@ if (btnSaveContact) {
toastr.success(editingId ? 'Contact Updated' : 'Contact Saved');
// Reset Form UI
- document.getElementById('editing_contact_id').value = '';
- document.getElementById('contact_name').value = '';
- document.getElementById('contact_mobile').value = '';
- document.getElementById('contact_designation').value = '';
- document.getElementById('contact_is_primary').checked = false;
-
- const saveBtn = document.getElementById('btnSaveContact');
- saveBtn.innerHTML = "โ Save";
+ resetContactForm();
+
// Refresh List
openEditLeadModal(leadId);
} else {
@@ -1857,7 +2015,11 @@ if (btnSaveContact) {
}
} catch (error) { console.error(error); }
};
- }
+}
+const btnClearContact = document.getElementById('btnClearContact');
+if (btnClearContact) {
+ btnClearContact.onclick = () => resetContactForm();
+}
const statusOrder = ['New', 'Potential', 'Prospects', 'Not a Prospects'];
@@ -1887,6 +2049,7 @@ if (btnSaveContact) {
+
function convertDBFormatted(input) {
if (!input) return null;
@@ -1916,4 +2079,277 @@ function convertDBFormatted(input) {
}
fetchLeads();
+// ================================================================
+// COMPANY SEARCH โ Odoo-style autocomplete
+// UNIFIED COMPANY SEARCH โ works for both Add & Edit modals
+// ================================================================
+
+// Context config for each modal
+const COMPANY_CTX = {
+ add: {
+ searchInput: 'companySearchInput',
+ dropdown: 'companyDropdown',
+ selected: 'companySelected',
+ selectedName: 'companySelectedName',
+ badge: 'shortNameBadge',
+ clientId: 'lead_client_id',
+ shortNameHidden:'lead_short_name_hidden',
+ formId: 'addLeadForm',
+ },
+ edit: {
+ searchInput: 'editCompanySearchInput',
+ dropdown: 'editCompanyDropdown',
+ selected: 'editCompanySelected',
+ selectedName: 'editCompanySelectedName',
+ badge: 'editShortNameBadge',
+ clientId: 'edit_lead_client_id',
+ shortNameHidden:'edit_lead_short_name_hidden',
+ formId: 'editLeadForm',
+ }
+};
+
+// Track state per modal
+const companyState = { add: { isExisting: false, selectedValue: '' },
+ edit: { isExisting: false, selectedValue: '' } };
+
+// Timer per modal
+const companyTimers = { add: null, edit: null };
+
+// โโ Called from oninput on both search inputs โโโโโโโโโโโโโโโโโโโโโ
+function searchCompany(val, mode) {
+ const ctx = COMPANY_CTX[mode];
+ const state = companyState[mode];
+ clearTimeout(companyTimers[mode]);
+
+ const dropdown = document.getElementById(ctx.dropdown);
+ const badge = document.getElementById(ctx.badge);
+
+ if (!val || val.trim().length < 1) {
+ dropdown.style.display = 'none';
+ badge.textContent = '';
+ badge.style.display = 'none';
+ document.getElementById(ctx.shortNameHidden).value = '';
+ document.getElementById(ctx.shortNameField).value = '';
+ state.selectedValue = ''; // โ clear selected value tracking
+ return;
+ }
+
+ // Only generate short name for new clients while typing
+ if (!state.isExisting) {
+ generateAndSetShortName(val, mode);
+ }
+
+ companyTimers[mode] = setTimeout(async () => {
+ try {
+ const res = await fetch(`= base_url('sales/searchClients') ?>?q=${encodeURIComponent(val)}`);
+ const json = await res.json();
+ renderCompanyDropdown(json.data || [], val, mode);
+ } catch (e) {
+ renderCompanyDropdown([], val, mode);
+ }
+ }, 250);
+}
+
+function renderCompanyDropdown(results, query, mode) {
+ const ctx = COMPANY_CTX[mode];
+ const dropdown = document.getElementById(ctx.dropdown);
+ dropdown.innerHTML = '';
+
+ // โโ Existing matches โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ results.forEach(client => {
+ const safe = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const highlighted = client.client_name.replace(
+ new RegExp(`(${safe})`, 'gi'),
+ '$1'
+ );
+ const initials = client.client_name.substring(0, 2).toUpperCase();
+
+ const item = document.createElement('div');
+ item.style.cssText = 'display:flex;align-items:center;gap:10px;padding:9px 14px;cursor:pointer;font-size:13px;border-bottom:1px solid #f5f5f5;';
+ item.innerHTML = `
+
+
+
+
+
+
${initials}
+
+
`;
+ item.onmouseenter = () => item.style.background = '#f7f9fc';
+ item.onmouseleave = () => item.style.background = '';
+ item.onclick = () => selectExistingCompany(client, mode);
+ dropdown.appendChild(item);
+ });
+
+ // โโ Divider โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ if (results.length > 0) {
+ const div = document.createElement('div');
+ div.style.cssText = 'height:1px;background:#eee;margin:4px 0;';
+ dropdown.appendChild(div);
+ }
+
+ // โโ Create new option โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const createItem = document.createElement('div');
+ createItem.style.cssText = 'display:flex;align-items:center;gap:8px;padding:10px 14px;cursor:pointer;font-size:13px;color:#185FA5;font-weight:500;';
+ createItem.innerHTML = `
+ +
+ Create "${query}"`;
+ createItem.onmouseenter = () => createItem.style.background = '#f0f7ff';
+ createItem.onmouseleave = () => createItem.style.background = '';
+ createItem.onclick = () => selectNewCompany(query, mode);
+ dropdown.appendChild(createItem);
+
+ dropdown.style.display = 'block';
+}
+
+// โโ User picked an EXISTING client from dropdown โโโโโโโโโโโโโโโโโโ
+function selectExistingCompany(client, mode) {
+ const ctx = COMPANY_CTX[mode];
+ const state = companyState[mode];
+ state.isExisting = true;
+ state.selectedValue = client.client_name;
+
+ document.getElementById(ctx.clientId).value = client.id;
+ document.getElementById(ctx.shortNameHidden).value = client.short_name || ''; // โ only this
+
+ const searchInp = document.getElementById(ctx.searchInput);
+ searchInp.value = client.client_name;
+ searchInp.style.display = 'none';
+ document.getElementById(ctx.selectedName).textContent = client.client_name;
+ document.getElementById(ctx.selected).style.display = 'flex';
+ document.getElementById(ctx.dropdown).style.display = 'none';
+
+ const badge = document.getElementById(ctx.badge);
+ badge.textContent = client.short_name || '';
+ badge.style.cssText = 'display:inline-block;background:#E6F1FB;color:#185FA5;' +
+ 'padding:2px 10px;border-radius:999px;font-size:11px;font-weight:600;' +
+ 'letter-spacing:0.5px;vertical-align:middle;';
+
+ const form = document.getElementById(ctx.formId);
+ const ef = form.querySelector('[name="email"]');
+ const pf = form.querySelector('[name="phone"]');
+ if (ef && client.email && !ef.value) ef.value = client.email;
+ if (pf && client.phone && !pf.value) pf.value = client.phone;
+}
+
+// โโ User clicked "Create new" โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+async function selectNewCompany(name, mode) {
+ // โโ Duplicate check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ try {
+ const res = await fetch(`= base_url('sales/checkDuplicate') ?>?table=clients&field=client_name&value=${encodeURIComponent(name)}`);
+ const json = await res.json();
+ if (json.exists) {
+ toastr.warning(`"${name}" already exists. Search and select it from the dropdown instead.`);
+ document.getElementById(COMPANY_CTX[mode].searchInput).value = '';
+ document.getElementById(COMPANY_CTX[mode].searchInput).focus();
+ return;
+ }
+ } catch(e) {
+ // silently allow on network error โ server will catch it
+ }
+ // โโ Rest of your existing code (unchanged) โโโโโโโโโโโ
+ const ctx = COMPANY_CTX[mode];
+ const state = companyState[mode];
+ state.isExisting = false;
+ state.selectedValue = name; // โ track what was confirmed as new
+ document.getElementById(ctx.clientId).value = '';
+ const inp = document.getElementById(ctx.searchInput);
+ inp.value = name;
+ document.getElementById(ctx.dropdown).style.display = 'none';
+ generateAndSetShortName(name, mode);
+
+}
+
+// โโ ร button on pill โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function clearCompanySelection(mode) {
+ const ctx = COMPANY_CTX[mode];
+ const state = companyState[mode];
+ state.isExisting = false;
+ state.selectedValue = '';
+
+ document.getElementById(ctx.clientId).value = '';
+ document.getElementById(ctx.shortNameHidden).value = ''; // โ only this
+ document.getElementById(ctx.selected).style.display = 'none';
+
+ const badge = document.getElementById(ctx.badge);
+ badge.textContent = '';
+ badge.style.display = 'none';
+
+ const inp = document.getElementById(ctx.searchInput);
+ inp.style.display = '';
+ inp.value = '';
+ inp.focus();
+
+ if (mode === 'add') {
+ const form = document.getElementById(ctx.formId);
+ const ef = form.querySelector('[name="email"]');
+ const pf = form.querySelector('[name="phone"]');
+ if (ef) ef.value = '';
+ if (pf) pf.value = '';
+ }
+}
+
+// โโ Short name generation (shared) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function generateAndSetShortName(name, mode) {
+ // FIX 2.2: trim ALL spaces before generating
+ const base = name.trim().replace(/\s+/g, '').substring(0, 10).toUpperCase();
+ if (!base) return;
+
+ checkShortNameInBothTables(base, (isDupe) => {
+ if (!isDupe) {
+ setShortName(base, mode);
+ } else {
+ let counter = 1;
+ const tryNext = () => {
+ const candidate = base.substring(0, 8) + String(counter).padStart(2, '0');
+ checkShortNameInBothTables(candidate, (exists) => {
+ if (exists) { counter++; tryNext(); }
+ else { setShortName(candidate, mode); }
+ });
+ };
+ tryNext();
+ }
+ });
+}
+
+function setShortName(val, mode) {
+ const ctx = COMPANY_CTX[mode];
+ document.getElementById(ctx.shortNameHidden).value = val; // โ only this
+
+ const badge = document.getElementById(ctx.badge);
+ badge.textContent = val;
+ badge.style.cssText = 'display:inline-block;background:#FAEEDA;color:#854F0B;' +
+ 'padding:2px 10px;border-radius:999px;font-size:11px;font-weight:600;' +
+ 'letter-spacing:0.5px;vertical-align:middle;';
+}
+
+function checkDuplicateTableFieldValue(table, field, value, callback) {
+ if (!value || value.trim() === '') { callback(false); return; }
+ fetch(`= base_url('sales/checkDuplicate') ?>?table=${encodeURIComponent(table)}&field=${encodeURIComponent(field)}&value=${encodeURIComponent(value)}`)
+ .then(res => res.json())
+ .then(json => callback(json.exists === true))
+ .catch(() => callback(false));
+}
+
+function checkShortNameInBothTables(value, callback) {
+ let results = { clients: false, leads: false };
+ let completed = 0;
+ const done = () => { completed++; if (completed === 2) callback(results.clients || results.leads); };
+ checkDuplicateTableFieldValue('clients', 'short_name', value, (d) => { results.clients = d; done(); });
+}
+
+// โโ Close dropdowns on outside click โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+document.addEventListener('click', (e) => {
+ ['add', 'edit'].forEach(mode => {
+ const ctx = COMPANY_CTX[mode];
+ if (!e.target.closest('#' + ctx.searchInput) && !e.target.closest('#' + ctx.dropdown)) {
+ const dd = document.getElementById(ctx.dropdown);
+ if (dd) dd.style.display = 'none';
+ }
+ });
+});
+
\ No newline at end of file
${highlighted}
+
+ ${client.short_name || 'N/A'}
+ ${client.email ? ` ${client.email}` : ''}
+ ${client.phone ? ` ยท ${client.phone}` : ''}
+
+