diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 387c3dfd..d7353cb7 100755
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -418,7 +418,7 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
$routes->get('getTheEmpDataForClaim/(:any)', 'ClientController::getTheEmpDataForClaim/$1');
// $routes->get('getTheEmpDataForClaimSearchByMobile/(:any)', 'ClientController::getTheEmpDataForClaimSearchByMobile/$1');
$routes->get('getTheEmpDataForClaimSearchByMobile', 'ClientController::getTheEmpDataForClaimSearchByMobile');
- $routes->get('getLeadNonEB/(:any)', 'LeadsController::getLeadNonEB/$1');
+ $routes->get('getLeadNonEB/(:any)/(:any)', 'LeadsController::getLeadNonEB/$1/$2');
$routes->get('getPolicyTypeFields', 'LeadsController::getPolicyTypeFields');
$routes->get('removeMultiFile', 'LeadsController::removeMultiFile');
$routes->get('removeInstallments', 'LeadsController::removeInstallments');
@@ -912,6 +912,10 @@ $routes->group('sales', function($routes) {
$routes->get('/', 'SalesController::index');
+ $routes->get('activities', 'SalesController::activities');
+
+ $routes->get('page/(:segment)', 'SalesController::noPage/$1');
+
// Get all leads with filters
$routes->get('leads', 'SalesController::getLeads');
diff --git a/app/Controllers/LeadsController.php b/app/Controllers/LeadsController.php
index b4eaac06..2c2d9bcf 100644
--- a/app/Controllers/LeadsController.php
+++ b/app/Controllers/LeadsController.php
@@ -363,8 +363,9 @@ class LeadsController extends BaseController
public function createLead()
{
- // print_r($this->request->getPost()); die;
$id = $this->request->getPost('id');
+ $actual_lead_id = $this->request->getPost('actual_lead_id');
+ $actual_lead_id = !empty($actual_lead_id) ? $actual_lead_id : null;
$postData = $this->request->getPost();
$data = $this->prepareLeadData();
$rules = [
@@ -4349,8 +4350,10 @@ class LeadsController extends BaseController
}
// get lead data for edit both EB and NON-EB
- public function getLeadNonEB($type, $id = null)
+ public function getLeadNonEB($type, $actual_lead_id = null ,$id = null)
{
+ // Convert '0' to null so it doesn't break your existing DB checks
+ $actual_lead_id = ($actual_lead_id == 0 || $actual_lead_id == '0') ? null : $actual_lead_id;
// Set basic data
$data = [
'issuer' => $this->issuer,
@@ -4365,6 +4368,7 @@ class LeadsController extends BaseController
'gpaClaimType' => $this->claim_type_for_gpa,
'causeOfDeath' => $this->cause_of_death,
'selected_lead_type' => $type,
+ 'actual_lead_id' => $actual_lead_id,
];
// Fetch sales team members who are active in team 5
diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php
index 047be5b3..904217b1 100644
--- a/app/Controllers/SalesController.php
+++ b/app/Controllers/SalesController.php
@@ -27,15 +27,107 @@ class SalesController extends BaseController
$this->noteModel = new SalesLeadNoteModel();
}
+
public function index() {
- $db = \Config\Database::connect();
- // Fetch users for the assignment dropdowns
- $data['users'] = $db->table('user_profiles')
- ->select('id, first_name, last_name')
- ->where('is_active', 1)
- ->get()->getResultArray();
+ $data = $this->getSalesStaffData();
+
+ $data['tab_name'] = 'Leads';
+ $data['page_name'] = 'Leads';
+
+ return $this->loadLayout('sales/tracker_view', $data);
+ }
- $this->loadLayout('sales/tracker_view', $data);
+ public function activities(){
+ $data = $this->getSalesStaffData();
+
+ $data['tab_name'] = 'Activities';
+ $data['page_name'] = 'Activities';
+
+ $data['leads'] = $this->leadModel->select('lead_id, company_name')
+ ->orderBy('lead_id', 'DESC')
+ ->findAll();
+
+ return $this->loadLayout('sales/activity_view', $data);
+ }
+
+ /**
+ * HELPER: Fetches Sales Managers based on the logged-in user's role and branch
+ */
+ private function getSalesStaffData(): array
+ {
+ $db = \Config\Database::connect();
+ $logged_user_id = get_session_userid();
+
+ $data = [
+ 'users' => [],
+ 'sales_manager_ids' => []
+ ];
+
+ // Get the Branch ID, Role, Team, and Name of the logged-in user
+ $row = $db->table('user_profiles up')
+ ->select('up.id, up.first_name, up.last_name, up.nhance_branch_id, up.role, ut.team_id')
+ ->join('user_teams ut', 'ut.user_id = up.id', 'left')
+ ->where('up.is_active', 1)
+ ->where('up.id', $logged_user_id)
+ ->get()
+ ->getRow();
+
+ $nhance_branch_id = $row ? $row->nhance_branch_id : null;
+ $role = $row ? $row->role : null;
+ $team_id = $row ? $row->team_id : null;
+
+ // Is the logged-in user a Sales Manager? (Role 4, Team 5)
+ if ($role == 4 && $team_id == 5) {
+
+ $data['sales_manager_ids'] = [$logged_user_id];
+
+ $data['users'] = [
+ [
+ 'id' => $row->id,
+ 'sales_manager' => trim($row->first_name . ' ' . $row->last_name),
+ 'nhance_branch_id' => $nhance_branch_id
+ ]
+ ];
+
+ }
+ // Otherwise fetch ALL sales managers in this branch
+ elseif ($nhance_branch_id) {
+
+ $data['users'] = $db->table('user_profiles up')
+ ->select('up.id, up.first_name, up.last_name, up.nhance_branch_id')
+ ->join('user_teams ut', 'ut.user_id = up.id')
+ ->where('up.is_active', 1)
+ ->where('ut.is_active', 1)
+ ->where('up.role', 4)
+ ->where('ut.team_id', 5)
+ ->where('up.nhance_branch_id', $nhance_branch_id)
+ ->get()
+ ->getResultArray();
+
+ $data['sales_manager_ids'] = array_column($data['users'], 'id');
+ }
+
+ return $data;
+ }
+
+ public function noPage($type = null)
+ {
+
+ $page_type = $type ?? 'Page';
+
+ $data['tab_name'] = 'Sales ' . $page_type;
+ $data['page_name'] = 'Sales ' . $page_type;
+
+ echo view('layout/header', $data);
+ echo '
+
+
+
Oops! ' . esc($page_type) . ' is under construction.
+
+
+
+
';
+ echo view('layout/footer', $data);
}
public function completeActivity($id) {
diff --git a/app/Models/LeadsModel.php b/app/Models/LeadsModel.php
index 1c028645..72ac2f59 100644
--- a/app/Models/LeadsModel.php
+++ b/app/Models/LeadsModel.php
@@ -10,6 +10,7 @@ class LeadsModel extends Model
protected $primaryKey = 'id';
protected $allowedFields = [
'id',
+ 'actual_lead_id',
'lead_type',
'issuer',
'client_type',
diff --git a/app/Models/SalesActivityModel.php b/app/Models/SalesActivityModel.php
index 80b8aef6..8d82097f 100644
--- a/app/Models/SalesActivityModel.php
+++ b/app/Models/SalesActivityModel.php
@@ -38,9 +38,9 @@ class SalesActivityModel extends Model
// Validation
protected $validationRules = [
'lead_id' => 'required|integer',
- 'activity_type' => 'required|in_list[Call,Email,Meeting,Demo,Share,Todo]',
+ 'activity_type' => 'required|in_list[Call,Email,Meeting,Demo,Share,Todo,Visit]',
'notes' => 'required',
- 'scheduled_date' => 'required|valid_date',
+ 'scheduled_date' => 'required',
'assigned_to' => 'required|integer',
'status' => 'in_list[pending,completed]',
];
@@ -80,35 +80,63 @@ class SalesActivityModel extends Model
*/
public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0)
{
- $builder = $this->select('sales_activities.*, actual_leads.company_name, user_profiles.first_name as assigned_to_name')
- ->join('actual_leads', 'actual_leads.lead_id = sales_activities.lead_id', 'left')
+ $this->select('sales_activities.*, sales_actual_leads.company_name, user_profiles.first_name as assigned_to_name')
+ ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id', 'left')
->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left');
if (!empty($filters['status'])) {
- $builder->where('sales_activities.status', $filters['status']);
+ $this->where('sales_activities.status', $filters['status']);
}
if (!empty($filters['activity_type'])) {
- $builder->where('sales_activities.activity_type', $filters['activity_type']);
+ $this->where('sales_activities.activity_type', $filters['activity_type']);
}
+ // if (!empty($filters['assigned_to'])) {
+ // $this->where('sales_activities.assigned_to', $filters['assigned_to']);
+ // }
if (!empty($filters['assigned_to'])) {
- $builder->where('sales_activities.assigned_to', $filters['assigned_to']);
+ $assigned_to = $filters['assigned_to'];
+
+ // If it's already an array, use it. If it's a string, explode it into an array.
+ $assignedToIds = is_array($assigned_to) ? $assigned_to : explode(',', $assigned_to);
+
+ // Now it is guaranteed to be an array, making whereIn perfectly safe
+ $this->whereIn('sales_actual_leads.assigned_to', $assignedToIds);
}
+
+
if (!empty($filters['date_from'])) {
- $builder->where('sales_activities.scheduled_date >=', $filters['date_from']);
+ $this->where('sales_activities.scheduled_date >=', $filters['date_from']);
}
if (!empty($filters['date_to'])) {
- $builder->where('sales_activities.scheduled_date <=', $filters['date_to']);
+ $this->where('sales_activities.scheduled_date <=', $filters['date_to']);
}
- return [
- 'data' => $builder->orderBy('sales_activities.scheduled_date', 'DESC')
- ->limit($limit, $offset)->findAll(),
- 'total' => $builder->countAllResults(false)
- ];
+ if (!empty($filters['search'])) {
+ $this->groupStart()
+ ->like('sales_actual_leads.company_name', $filters['search'])
+ ->orLike('sales_actual_leads.email', $filters['search'])
+ ->orLike('sales_actual_leads.phone', $filters['search'])
+ ->orLike('user_profiles.first_name', $filters['search'])
+ ->groupEnd();
+ }
+
+ $this->orderBy('sales_activities.created_at', 'DESC');
+
+ $total = $this->countAllResults(false);
+
+ $data = $this->findAll($limit, $offset);
+
+ return ['data' => $data,'total' => $total];
+
+ // return [
+ // 'data' => $builder->orderBy('sales_activities.scheduled_date', 'DESC')
+ // ->limit($limit, $offset)->findAll(),
+ // 'total' => $builder->countAllResults(false)
+ // ];
}
/**
@@ -142,8 +170,8 @@ class SalesActivityModel extends Model
{
$endDate = date('Y-m-d H:i:s', strtotime("+{$days} days"));
- return $this->select('sales_activities.*, actual_leads.company_name')
- ->join('actual_leads', 'actual_leads.lead_id = sales_activities.lead_id', 'left')
+ return $this->select('sales_activities.*, sales_actual_leads.company_name')
+ ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id', 'left')
->where('sales_activities.assigned_to', $userId)
->where('sales_activities.status', 'pending')
->where('sales_activities.scheduled_date <=', $endDate)
diff --git a/app/Models/SalesActualLeadModel.php b/app/Models/SalesActualLeadModel.php
index 25603740..22db6a2a 100644
--- a/app/Models/SalesActualLeadModel.php
+++ b/app/Models/SalesActualLeadModel.php
@@ -37,23 +37,37 @@ class SalesActualLeadModel extends Model
// Validation
protected $validationRules = [
- 'company_name' => 'required|min_length[2]|max_length[255]',
- 'email' => 'required|valid_email|max_length[255]',
- 'phone' => 'required|min_length[10]|max_length[20]',
- 'status' => 'in_list[New,Potential,Prospects,Non prospects]',
+ 'company_name' => 'required|alpha_space|min_length[2]|max_length[255]',
+ 'email' => 'required_without[phone]|permit_empty|valid_email|max_length[255]',
+ 'phone' => 'required_without[email]|permit_empty|regex_match[/^[0-9+\s]+$/]|min_length[10]|max_length[20]',
+ 'status' => 'in_list[New,Potential,Prospects,Not a Prospects]',
+ 'assigned_to' => 'required',
+ 'gst_number' => 'permit_empty|exact_length[15]|regex_match[/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][0-9A-Z]Z[0-9A-Z]$/]',
];
protected $validationMessages = [
'company_name' => [
- 'required' => 'Company name is required',
+ 'required' => 'Company Name is Missing',
+ 'alpha_space' => 'Company Name must contain only letters and spaces',
+ 'min_length' => 'Company Name must be at least 2 characters long',
+ 'max_length' => 'Company Name must be at most 255 characters long'
],
'email' => [
- 'required' => 'Email is required',
+ 'required_without' => 'Either Email or Mobile Number is Needed.',
'valid_email' => 'Please provide a valid email address',
+ 'max_length' => 'Email must be at most 255 characters long',
],
'phone' => [
- 'required' => 'Phone number is required',
+ 'required_without' => 'Either Email or Mobile Number is Needed.',
+ 'regex_match' => 'Phone number can contain only digits, + and spaces.',
+ 'min_length' => 'Mobile Number must be at least 10 characters long',
+ 'max_length' => 'Mobile Number must be at most 20 characters long',
],
+ 'status' => [ 'in_list' => 'Please select a valid Status'],
+ 'assigned_to'=> [ 'required' => 'Please select a valid User' ],
+ 'gst_number' => [ 'exact_length' => 'GST Number must be exactly 15 characters long.',
+ 'regex_match' => 'Please enter a valid GST Number (e.g., 22AAAAA0000A1Z5).'
+ ],
];
protected $skipValidation = false;
@@ -74,29 +88,42 @@ class SalesActualLeadModel extends Model
*/
public function getLeadsWithFilters($filters = [], $limit = 10, $offset = 0)
{
- $builder = $this->select('sales_actual_leads.*, user_profiles.first_name as assigned_to_name')
+ $this->select('sales_actual_leads.*, user_profiles.first_name as assigned_to_name, user_profiles.email as assigned_to_email')
->join('user_profiles', 'user_profiles.id = sales_actual_leads.assigned_to', 'left');
if (!empty($filters['status'])) {
- $builder->where('sales_actual_leads.status', $filters['status']);
+ $this->where('sales_actual_leads.status', $filters['status']);
}
+ // if (!empty($filters['assigned_to'])) {
+ // $this->where('sales_actual_leads.assigned_to', $filters['assigned_to']);
+ // }
if (!empty($filters['assigned_to'])) {
- $builder->where('sales_actual_leads.assigned_to', $filters['assigned_to']);
+ $assigned_to = $filters['assigned_to'];
+
+ // If it's already an array, use it. If it's a string, explode it into an array.
+ $assignedToIds = is_array($assigned_to) ? $assigned_to : explode(',', $assigned_to);
+
+ // Now it is guaranteed to be an array, making whereIn perfectly safe
+ $this->whereIn('sales_actual_leads.assigned_to', $assignedToIds);
}
+ $this->orderBy('sales_actual_leads.created_at', 'DESC');
+
if (!empty($filters['search'])) {
- $builder->groupStart()
+ $this->groupStart()
->like('sales_actual_leads.company_name', $filters['search'])
->orLike('sales_actual_leads.email', $filters['search'])
->orLike('sales_actual_leads.phone', $filters['search'])
+ ->orLike('user_profiles.first_name', $filters['search'])
->groupEnd();
}
- return [
- 'data' => $builder->limit($limit, $offset)->findAll(),
- 'total' => $builder->countAllResults(false)
- ];
+ $total = $this->countAllResults(false);
+
+ $data = $this->findAll($limit, $offset);
+
+ return ['data' => $data,'total' => $total];
}
/**
@@ -113,9 +140,11 @@ class SalesActualLeadModel extends Model
$contactModel = new SalesContactPersonModel();
$activityModel = new SalesActivityModel();
$noteModel = new SalesLeadNoteModel();
+ $leadsModel = new LeadsModel();
$lead['contact_persons'] = $contactModel->where('lead_id', $leadId)->findAll();
$lead['activities'] = $activityModel->getActivitiesByLead($leadId);
+ $lead['opportunities'] = $leadsModel->where('actual_lead_id', $leadId)->findAll();
$lead['notes'] = $noteModel->getNotesByLead($leadId);
return $lead;
diff --git a/app/Views/layout/header.php b/app/Views/layout/header.php
index 57aff2c2..61e63b23 100755
--- a/app/Views/layout/header.php
+++ b/app/Views/layout/header.php
@@ -2077,6 +2077,37 @@
+
+
+
+ /assets/images/sales_tracker_dark_sb.png" alt="Logo" height="20">
+ Sales Tracker
+
+
+
+
diff --git a/app/Views/leads_form.php b/app/Views/leads_form.php
index ef30ba59..87868d39 100644
--- a/app/Views/leads_form.php
+++ b/app/Views/leads_form.php
@@ -131,6 +131,8 @@
enctype="multipart/form-data">
+
+
@@ -741,6 +743,7 @@
$('#page_title').text(page_title);
$('#leads_primarykey').val(res.data.id);
+ $('#actual_lead_id').val(res.data.actual_lead_id || 0);
$('#policy_start_date').val(res.data.policy_end_date);
$('#lead_type').val(res.data.lead_type);
$('#issuer').val(res.data.issuer);
diff --git a/app/Views/leads_form_handler.php b/app/Views/leads_form_handler.php
index 6677520d..01960410 100644
--- a/app/Views/leads_form_handler.php
+++ b/app/Views/leads_form_handler.php
@@ -956,6 +956,7 @@ if (isset($selected_lead_type)) {
$('#page_title').text('Edit Lead');
$('#leads_primarykey').val(data.id || '');
+ $('#actual_lead_id').val(data.actual_lead_id || 0);
$('#policy_start_date').val(data.policy_end_date || '');
$('#lead_type').val(data.lead_type || '');
$('#issuer').val(data.issuer || '');
@@ -1130,6 +1131,7 @@ if (isset($selected_lead_type)) {
$('#page_title').text('Edit Lead');
$('#leads_primarykey').val(data.id || '');
+ $('#actual_lead_id').val(data.actual_lead_id || 0);
$('#policy_start_date').val(data.policy_end_date || '');
$('#lead_type').val(data.lead_type || '');
$('#issuer').val(data.issuer || '');
diff --git a/app/Views/leads_list.php b/app/Views/leads_list.php
index 896a1117..98b9f232 100644
--- a/app/Views/leads_list.php
+++ b/app/Views/leads_list.php
@@ -533,8 +533,11 @@ table.dataTable tbody td {
console.log('lead_id', lead_id);
console.log('lead_form_type', lead_form_type);
-
- let url = '=base_url('/util/getLeadNonEB/')?>' + lead_form_type + '/' + lead_id
+ let actual_lead_id = 0; // Hardcoded to 0 since this is an edit operation
+ //Here After Edit URL: /type / actual_lead_id (0) / lead_id
+ let url = '=base_url('/util/getLeadNonEB/')?>' + lead_form_type + '/' + actual_lead_id + '/' + lead_id;
+ // old URL
+ // let url = '=base_url('/util/getLeadNonEB/')?>' + lead_form_type + '/' + lead_id
console.log('url ', url)
window.location.href = url;
diff --git a/app/Views/leads_non_eb.php b/app/Views/leads_non_eb.php
index 6456935a..6d2049f0 100644
--- a/app/Views/leads_non_eb.php
+++ b/app/Views/leads_non_eb.php
@@ -74,6 +74,7 @@
+
diff --git a/app/Views/sales/activity_view.php b/app/Views/sales/activity_view.php
new file mode 100644
index 00000000..4c5f939b
--- /dev/null
+++ b/app/Views/sales/activity_view.php
@@ -0,0 +1,1744 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Activities
+ Opportunities
+
+
+
+
+
Activity TimeLine
+ + Add Activity
+
+
+
+
+
+
+
Opportunity
+ + Add Opportunity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Outcome / Notes *
+
+
+
+ Next Follow-up? *
+
+ No
+ Yes
+
+
+
+
Next Activity Type *
+
+
+ 📞 Call
+ ✉️ Email
+ 📅 Meeting
+ 🚗 Visit
+ 🎬 Demo
+ 📄 Share Docs
+ ✓ To Do
+
+
+
+
+ Follow-up Notes *
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/sales/tracker_view.php b/app/Views/sales/tracker_view.php
index b317712c..d2c3085d 100644
--- a/app/Views/sales/tracker_view.php
+++ b/app/Views/sales/tracker_view.php
@@ -2,19 +2,20 @@
/* 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; }
+ .main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
+ /* .top-bar { background: white; padding: 15px 30px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } */
+
.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; }
+ .filter-tabs { display: flex; gap: 10px; padding: 20px 30px; }
.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 */
+ .lead-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; /* responsive */ gap: 15px; }
+ .lead-actions { display: flex; align-items: center; gap: 10px; }
.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); }
@@ -22,12 +23,13 @@
.status-new { background: #e3f2fd; color: #1976d2; }
.status-potential { background: #fff3e0; color: #f57c00; }
.status-prospects { background: #e8f5e9; color: #388e3c; }
+ .status-not-a-prospects { background: #ffebee; color: #d32f2f; }
/* 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-header { padding: 5px 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; }
@@ -39,6 +41,14 @@
.activity-type-btn:hover { border-color: #ff6b35; background: #fff5f2; }
.activity-type-btn.active { border-color: #ff6b35; background: #ff6b35; color: white; }
+ .activity-types { display: grid; grid-template-columns: repeat(12, 1fr); /* 12-column grid */ gap: 10px; margin-bottom: 20px; }
+
+ /* First 3 buttons → 4 columns each (3 × 4 = 12) */
+ .activity-types button:nth-child(-n+3) { grid-column: span 4; }
+
+ /* Next 4 buttons → 3 columns each (4 × 3 = 12) */
+ .activity-types button:nth-child(n+4) { grid-column: span 3; }
+
/* Timeline Styling */
.timeline { position: relative; padding-left: 30px; margin-top: 20px; }
.timeline-item { position: relative; padding-bottom: 25px; }
@@ -47,71 +57,167 @@
.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;}
+ .timeline::before { display: none !important; content: none !important; }
+ .timeline.no-line::before { display: none; }
+ .timeline { border-left: none !important; }
+ .timeline-item:last-child { border-left: none !important;}
+ .timeline-item:last-child::before { display: none !important;}
+
+ /* opportunities Styling */
+ .opportunities-list { display: grid; gap: 15px;}
+ .opportunity-card { background: white; border-radius: 12px; padding: 20px; border: 1px solid #e0e0e0; transition: all 0.2s;}
+ .opportunity-header { display: flex; justify-content: space-between; align-items: start; margin-bottom: 15px;}
+ .opportunity-title { font-weight: 600; font-size: 16px; margin-bottom: 5px;}
+ .opportunity-amount { font-size: 20px; font-weight: 700; color: #4caf50;}
+ .opportunity-details { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px;}
+ .opportunity-detail-item { font-size: 13px; color: #666;}
+ .opportunity-footer { margin-top: 15px; padding-top: 15px; border-top: 1px solid #f0f0f0; font-size: 13px; color: #666; }
+
+ /* Base style for both tabs */
+ .tab-item { cursor: pointer; padding-bottom: 10px; margin: 0; font-size: 16px; color: #999; /* Default grey for unselected */ border-bottom: 2px solid transparent; transition: all 0.2s ease; }
+
+ /* Style for the SELECTED tab */
+ .tab-item.active { color: #333; /* Darker text */ border-bottom: 2px solid #ff6b35; /* Orange underline */ }
+
+ .empty-state { text-align: center; padding: 60px 20px; color: #999; }
+ .empty-icon { width: 80px; height: 80px; margin: 0 auto 20px; background: #f5f5f5; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 36px; }
+ input, select, textarea { background-color: white !important; border-radius: 6px !important; box-shadow: none !important; border-color: #ddd !important; }
+ input, select, textarea { width: 400px; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; outline: none; }
+ input:focus, select:focus, textarea:focus { border-color: #ff6b35; outline: none; /* removes blue browser outline */ }
+ .modal .form-control { transition: 0.2s ease; }
+
+ .modal .form-control:focus { border-color: #ff6b35; box-shadow: 0 0 0 2px rgba(255, 107, 53, 0.2);
+ outline: none;
+ }
+
+ /* The animation definition */
+ @keyframes validation-blink {
+ 0% { transform: translateX(0); box-shadow: 0 0 0px red; }
+ 25% { transform: translateX(-5px); box-shadow: 0 0 10px red; }
+ 50% { transform: translateX(5px); box-shadow: 0 0 10px red; }
+ 75% { transform: translateX(-5px); box-shadow: 0 0 10px red; }
+ 100% { transform: translateX(0); box-shadow: 0 0 0px red; }
+ }
+
+ /* The class we will add via JavaScript */
+ .blink-error {
+ animation: validation-blink 0.3s ease-in-out;
+ border-color: #ff4d4d !important;
+ }
+
-
-
-
@@ -119,23 +225,42 @@
+
-
-
-
-
-
+
-
-
Activity Timeline
-
+ Add Activity
+
+
Activities
+ Opportunities
-
+
+
+
+
Activity TimeLine
+ + Add Activity
+
+
+
+
+
+
+
Opportunity
+ + Add Opportunity
+
+
+
+
@@ -144,82 +269,407 @@
-
+
+
+
+
Activity Type
📞 Call
✉️ Email
📅 Meeting
+ 🚗 Visit
🎬 Demo
📄 Share Docs
✓ To Do
- Notes
+ Notes *
-
- Create Activity
-
+
+
-
+
-
+
+
+
+
+
- Outcome Notes
-
+ Outcome / Notes *
+
- Next Follow-up?
-
+ Next Follow-up? *
+
No
Yes
+
Next Activity Type *
+
+
+ 📞 Call
+ ✉️ Email
+ 📅 Meeting
+ 🚗 Visit
+ 🎬 Demo
+ 📄 Share Docs
+ ✓ To Do
+
+
+
- Date
-
+ Follow-up Notes *
+
+
+
-
Submit Outcome
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/assets/images/sales_tracker_dark_sb.png b/public/assets/images/sales_tracker_dark_sb.png
new file mode 100644
index 00000000..c38dd31f
Binary files /dev/null and b/public/assets/images/sales_tracker_dark_sb.png differ