356 lines
18 KiB
PHP
356 lines
18 KiB
PHP
<style>
|
|
/* POC Exact Styling */
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
|
|
.main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; height: 100vh; }
|
|
.top-bar { background: white; padding: 15px 30px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
|
|
.search-input { width: 400px; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; outline: none; }
|
|
.search-input:focus { border-color: #ff6b35; }
|
|
.btn-primary { background: #ff6b35; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
|
.btn-primary:hover { background: #ff5722; transform: translateY(-1px); }
|
|
|
|
/* Filter Tabs */
|
|
.filter-tabs { display: flex; gap: 10px; padding: 20px 30px; background: #f5f5f5; }
|
|
.tab { padding: 10px 20px; border-radius: 20px; cursor: pointer; font-size: 14px; background: white; color: #666; border: 1px solid #e0e0e0; transition: all 0.2s; }
|
|
.tab.active { background: #ff6b35; color: white; border-color: #ff6b35; }
|
|
|
|
/* Leads Grid */
|
|
.leads-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 0 30px 30px; overflow-y: auto; }
|
|
.lead-card { background: white; border-radius: 12px; padding: 20px; border: 1px solid #e0e0e0; cursor: pointer; transition: all 0.2s; }
|
|
.lead-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); transform: translateY(-2px); }
|
|
.lead-status { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 500; margin-top: 5px; }
|
|
.status-new { background: #e3f2fd; color: #1976d2; }
|
|
.status-potential { background: #fff3e0; color: #f57c00; }
|
|
.status-prospects { background: #e8f5e9; color: #388e3c; }
|
|
|
|
/* Modals */
|
|
.modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 2000; align-items: center; justify-content: center; }
|
|
.modal.active { display: flex; }
|
|
.modal-content { background: white; border-radius: 12px; width: 90%; max-width: 800px; max-height: 90vh; overflow-y: auto; display: flex; flex-direction: column; }
|
|
.modal-header { padding: 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
|
|
.modal-body { padding: 25px; flex: 1; }
|
|
.form-group { margin-bottom: 20px; }
|
|
.form-label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; }
|
|
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
|
|
|
|
/* Activity Type Buttons (POC Style) */
|
|
.activity-types { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px; }
|
|
.activity-type-btn { padding: 12px; border: 1px solid #e0e0e0; background: white; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 14px; transition: 0.2s; }
|
|
.activity-type-btn:hover { border-color: #ff6b35; background: #fff5f2; }
|
|
.activity-type-btn.active { border-color: #ff6b35; background: #ff6b35; color: white; }
|
|
|
|
/* Timeline Styling */
|
|
.timeline { position: relative; padding-left: 30px; margin-top: 20px; }
|
|
.timeline-item { position: relative; padding-bottom: 25px; }
|
|
.timeline-item::before { content: ''; position: absolute; left: -21px; top: 10px; width: 2px; height: 100%; background: #e0e0e0; }
|
|
.timeline-dot { position: absolute; left: -26px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: #ff6b35; border: 2px solid white; box-shadow: 0 0 0 1px #ff6b35; }
|
|
.timeline-dot.completed { background: #4caf50; box-shadow: 0 0 0 1px #4caf50; }
|
|
.timeline-content { background: #f8f8f8; padding: 15px; border-radius: 8px; }
|
|
.timeline-item {display : block !important;}
|
|
|
|
</style>
|
|
|
|
<div class="main-content">
|
|
<div class="top-bar">
|
|
<input type="text" class="search-input" id="mainSearch" placeholder="Search leads..." onkeyup="fetchLeads()">
|
|
<button class="btn-primary" onclick="openModal('addLeadModal')">+ Add Lead</button>
|
|
</div>
|
|
|
|
<div class="filter-tabs">
|
|
<div class="tab active" data-filter="all" onclick="setFilter('all', this)">All</div>
|
|
<div class="tab" data-filter="New" onclick="setFilter('New', this)">New</div>
|
|
<div class="tab" data-filter="Potential" onclick="setFilter('Potential', this)">Potential</div>
|
|
<div class="tab" data-filter="Prospects" onclick="setFilter('Prospects', this)">Prospects</div>
|
|
</div>
|
|
|
|
<div class="leads-grid" id="leadsGrid"></div>
|
|
</div>
|
|
|
|
<div class="modal" id="addLeadModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>Add New Lead</h2>
|
|
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('addLeadModal')">×</button>
|
|
</div>
|
|
<form id="addLeadForm" class="modal-body">
|
|
<div class="form-group">
|
|
<label class="form-label">Company Name</label>
|
|
<input type="text" name="company_name" class="search-input" style="width:100%" required>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Email</label>
|
|
<input type="email" name="email" class="search-input" style="width:100%" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Phone</label>
|
|
<input type="text" name="phone" class="search-input" style="width:100%" required>
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Status</label>
|
|
<select name="status" class="search-input" style="width:100%" required>
|
|
<option value="New">New</option>
|
|
<option value="Potential">Potential</option>
|
|
<option value="Prospects">Prospects</option>
|
|
<option value="Non prospects">Non prospects</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Assign To</label>
|
|
<select name="assigned_to" class="search-input" style="width:100%">
|
|
<?php foreach($users as $user): ?>
|
|
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?> </option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div style="text-align:right; border-top:1px solid #eee; padding-top:20px;">
|
|
<button type="button" class="btn-primary" style="background:#eee; color:#333; margin-right:10px;" onclick="closeModal('addLeadModal')">Cancel</button>
|
|
<button type="submit" class="btn-primary">Create Lead</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal" id="leadDetailModal">
|
|
<div class="modal-content" style="max-width: 850px;">
|
|
<div class="modal-header">
|
|
<div>
|
|
<h2 id="det_company">Lead Detail</h2>
|
|
<span id="det_status_badge" class="lead-status"></span>
|
|
</div>
|
|
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('leadDetailModal')">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div style="background:#f8f8f8; padding:20px; border-radius:12px; margin-bottom:20px; display:grid; grid-template-columns: 1fr 1fr; gap:15px;">
|
|
<div><small style="color:#999">Email</small><div id="det_email" style="font-weight:500"></div></div>
|
|
<div><small style="color:#999">Phone</small><div id="det_phone" style="font-weight:500"></div></div>
|
|
<div><small style="color:#999">Owner</small><div id="det_owner" style="font-weight:500"></div></div>
|
|
<div><small style="color:#999">Address</small><div id="det_address" style="font-weight:500">N/A</div></div>
|
|
</div>
|
|
|
|
<div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #eee; margin-bottom:20px;">
|
|
<h3 style="padding-bottom:10px; border-bottom:2px solid #ff6b35;">Activity Timeline</h3>
|
|
<button class="btn-primary" style="padding:6px 15px; font-size:12px;" onclick="openActivityModal()">+ Add Activity</button>
|
|
</div>
|
|
<div id="timelineContainer" class="timeline"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal" id="activityModal">
|
|
<div class="modal-content" style="max-width: 600px;">
|
|
<div class="modal-header">
|
|
<h2>Add Activity</h2>
|
|
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('activityModal')">×</button>
|
|
</div>
|
|
<form id="activityForm" class="modal-body">
|
|
<input type="hidden" id="act_lead_id">
|
|
<label class="form-label">Activity Type</label>
|
|
<div class="activity-types" id="typeButtons">
|
|
<button type="button" class="activity-type-btn active" onclick="selectType('Call', this)">📞 Call</button>
|
|
<button type="button" class="activity-type-btn" onclick="selectType('Email', this)">✉️ Email</button>
|
|
<button type="button" class="activity-type-btn" onclick="selectType('Meeting', this)">📅 Meeting</button>
|
|
<button type="button" class="activity-type-btn" onclick="selectType('Demo', this)">🎬 Demo</button>
|
|
<button type="button" class="activity-type-btn" onclick="selectType('Share', this)">📄 Share Docs</button>
|
|
<button type="button" class="activity-type-btn" onclick="selectType('Todo', this)">✓ To Do</button>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Notes</label>
|
|
<textarea id="act_notes" class="search-input" style="width:100%; height:100px;" placeholder="Add notes about this activity..." required></textarea>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Scheduled Date & Time</label>
|
|
<input type="datetime-local" id="act_date" class="search-input" style="width:100%" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Assigned To</label>
|
|
<select id="act_owner" class="search-input" style="width:100%">
|
|
<?php foreach($users as $user): ?>
|
|
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div style="text-align:right; margin-top:10px;">
|
|
<button type="submit" class="btn-primary" style="width:100%">Create Activity</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal" id="completeModal">
|
|
<div class="modal-content" style="max-width: 500px;">
|
|
<div class="modal-header">
|
|
<h3>Complete Activity</h3>
|
|
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" onclick="closeModal('completeModal')">×</button>
|
|
</div>
|
|
<form id="completeForm" class="modal-body">
|
|
<input type="hidden" id="comp_id">
|
|
<div class="form-group">
|
|
<label class="form-label">Outcome Notes</label>
|
|
<textarea id="comp_notes" class="search-input" style="width:100%; height:80px;" required></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Next Follow-up?</label>
|
|
<select id="do_follow" class="search-input" onchange="document.getElementById('f_up').style.display=this.value==='yes'?'block':'none'">
|
|
<option value="no">No</option>
|
|
<option value="yes">Yes</option>
|
|
</select>
|
|
</div>
|
|
<div id="f_up" style="display:none; border-top:1px dashed #ccc; padding-top:15px;">
|
|
<div class="form-group">
|
|
<label class="form-label">Date</label>
|
|
<input type="datetime-local" id="f_date" class="search-input">
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn-primary" style="width:100%; margin-top:15px;">Submit Outcome</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API = '<?= base_url('sales') ?>';
|
|
let filter = 'all';
|
|
let lead_id = null;
|
|
let selectedType = 'Call';
|
|
|
|
function openModal(id) { document.getElementById(id).classList.add('active'); }
|
|
function closeModal(id) { document.getElementById(id).classList.remove('active'); }
|
|
|
|
function selectType(val, el) {
|
|
document.querySelectorAll('.activity-type-btn').forEach(b => b.classList.remove('active'));
|
|
el.classList.add('active');
|
|
selectedType = val;
|
|
}
|
|
|
|
// 1. Leads Grid Logic
|
|
async function fetchLeads() {
|
|
const q = document.getElementById('mainSearch').value;
|
|
const res = await fetch(`${API}/leads?status=${filter==='all'?'':filter}&search=${q}`);
|
|
const json = await res.json();
|
|
|
|
document.getElementById('leadsGrid').innerHTML = json.data.map(l => `
|
|
<div class="lead-card" onclick="viewDetail(${l.lead_id})">
|
|
<div style="font-weight:600; font-size:17px;">${l.company_name}</div>
|
|
<span class="lead-status status-${l.status.toLowerCase().replace(' ', '-')}">${l.status}</span>
|
|
<div style="margin-top:12px; color:#666; font-size:13px;">
|
|
<div>✉️ ${l.email}</div>
|
|
<div>📞 ${l.phone}</div>
|
|
</div>
|
|
<div style="margin-top:15px; border-top:1px solid #f0f0f0; padding-top:10px; font-size:12px;">
|
|
Assigned to: <b>${l.assigned_to_name || 'Unassigned'}</b>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// 2. Detail Logic
|
|
async function viewDetail(id) {
|
|
lead_id = id;
|
|
const res = await fetch(`${API}/leads/${id}`);
|
|
const json = await res.json();
|
|
const l = json.data;
|
|
|
|
document.getElementById('det_company').innerText = l.company_name;
|
|
document.getElementById('det_email').innerText = l.email;
|
|
document.getElementById('det_phone').innerText = l.phone;
|
|
document.getElementById('det_owner').innerText = l.assigned_to_name;
|
|
document.getElementById('det_address').innerText = l.address || 'N/A';
|
|
|
|
const badge = document.getElementById('det_status_badge');
|
|
badge.innerText = l.status;
|
|
badge.className = `lead-status status-${l.status.toLowerCase().replace(' ', '-')}`;
|
|
|
|
renderTimeline(l.activities);
|
|
openModal('leadDetailModal');
|
|
}
|
|
|
|
function renderTimeline(acts) {
|
|
const cont = document.getElementById('timelineContainer');
|
|
cont.innerHTML = acts.length ? acts.map(a => `
|
|
<div class="timeline-item">
|
|
<div class="timeline-dot ${a.status==='completed'?'completed':''}"></div>
|
|
<div class="timeline-content">
|
|
<div style="display:flex; justify-content:space-between; margin-bottom:5px;">
|
|
<b>${a.activity_type}</b>
|
|
<span style="font-size:11px; color:#999">${a.scheduled_date}</span>
|
|
</div>
|
|
<div style="font-size:13px; color:#444;">${a.notes}</div>
|
|
${a.status === 'pending' ?
|
|
`<button class="btn-primary" style="padding:4px 10px; font-size:11px; margin-top:8px;" onclick="openComp(${a.activity_id})">Mark Complete</button>` :
|
|
`<div style="font-size:12px; color:#388e3c; margin-top:8px; font-weight:500;">✓ Outcome: ${a.completion_notes}</div>`
|
|
}
|
|
</div>
|
|
</div>
|
|
`).join('') : '<p style="color:#bbb; text-align:center;">No activities logged yet.</p>';
|
|
}
|
|
|
|
function openActivityModal() {
|
|
document.getElementById('act_lead_id').value = lead_id;
|
|
openModal('activityModal');
|
|
}
|
|
|
|
function openComp(id) {
|
|
document.getElementById('comp_id').value = id;
|
|
openModal('completeModal');
|
|
}
|
|
|
|
// 3. Form Submissions
|
|
document.getElementById('addLeadForm').onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const data = Object.fromEntries(new FormData(e.target).entries());
|
|
const res = await fetch(`${API}/leads`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(data)
|
|
});
|
|
if(res.ok) { closeModal('addLeadModal'); fetchLeads(); e.target.reset(); }
|
|
else { const err = await res.json(); alert(err.messages.status || 'Error adding lead'); }
|
|
};
|
|
|
|
document.getElementById('activityForm').onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const payload = {
|
|
lead_id: document.getElementById('act_lead_id').value,
|
|
activity_type: selectedType,
|
|
notes: document.getElementById('act_notes').value,
|
|
scheduled_date: document.getElementById('act_date').value,
|
|
assigned_to: document.getElementById('act_owner').value,
|
|
status: 'pending'
|
|
};
|
|
const res = await fetch(`${API}/activities`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
if(res.ok) { closeModal('activityModal'); viewDetail(lead_id); }
|
|
};
|
|
|
|
document.getElementById('completeForm').onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const payload = {
|
|
completion_notes: document.getElementById('comp_notes').value,
|
|
create_followup: document.getElementById('do_follow').value === 'yes',
|
|
followup_date: document.getElementById('f_date').value
|
|
};
|
|
const res = await fetch(`${API}/activities/${document.getElementById('comp_id').value}/complete`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
if(res.ok) { closeModal('completeModal'); viewDetail(lead_id); }
|
|
};
|
|
|
|
function setFilter(val, el) {
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
el.classList.add('active');
|
|
filter = val;
|
|
fetchLeads();
|
|
}
|
|
|
|
fetchLeads();
|
|
</script>
|