sales tracker : GWM
This commit is contained in:
parent
04cd132808
commit
a1d5e97259
@ -786,7 +786,7 @@ $routes->get('FhplGetBenefDetails','FhplApiController::FhplGetBenefDetails');
|
||||
$routes->get('EcardRequest','HealthIndiaApiController::EcardRequest');
|
||||
$routes->get('HospitalNetwork','MediAssistApiController::HospitalNetwork');
|
||||
$routes->get('VidalGetBenefDetails','VidalApiController::VidalGetBenefDetails');
|
||||
$routes->get('ClaimDetail','HealthIndiaApiController::ClaimDetail');
|
||||
$routes->get('ClaimDetail','VidalApiController::ClaimDetail');
|
||||
$routes->get('SubmitClaim','HealthIndiaApiController::SubmitClaim');
|
||||
$routes->get('IntimateClaim','MediAssistApiController::IntimateClaim');
|
||||
$routes->get('IRSubmission','MediAssistApiController::IRSubmission');
|
||||
@ -895,6 +895,8 @@ $routes->group('logs', function($routes) {
|
||||
$routes->group('sales', function($routes) {
|
||||
|
||||
// ==================== LEAD ROUTES ====================
|
||||
|
||||
$routes->get('/', 'SalesController::index');
|
||||
|
||||
// Get all leads with filters
|
||||
$routes->get('leads', 'SalesController::getLeads');
|
||||
|
||||
@ -1,259 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class LogController extends BaseController
|
||||
{
|
||||
private $logPath;
|
||||
public $dModel;
|
||||
public $session;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Path to log files
|
||||
$this->logPath = WRITEPATH . 'logs/';
|
||||
$this->session = session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display list of all log files
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
||||
$logFiles = $this->getLogFiles();
|
||||
|
||||
$data = [
|
||||
'title' => 'Log Files',
|
||||
'logFiles' => $logFiles
|
||||
'title' => 'Log Files',
|
||||
'logFiles' => $this->getLogFiles()
|
||||
];
|
||||
|
||||
return $this->loadLayout('logs/index', $data);
|
||||
|
||||
// return view('logs/index', $data);
|
||||
return view('logs/index', $data);
|
||||
}
|
||||
|
||||
public function view($filename = null)
|
||||
{
|
||||
if (!$filename) return redirect()->to('/logs');
|
||||
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) return redirect()->to('/logs');
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// 1. Fetch TPA List for the first row of tabs
|
||||
$tpaConfigs = $db->table('tpa_log_config')->get()->getResultArray();
|
||||
|
||||
// 2. Dynamically get Action buttons from table columns
|
||||
$allColumns = $db->getFieldNames('tpa_log_config');
|
||||
$dynamicKeys = [];
|
||||
foreach ($allColumns as $column) {
|
||||
if ($column !== 'tpa_name') {
|
||||
// Formatting: 'claim_push_key' -> 'Claim Push'
|
||||
$label = str_replace(['_key', '_'], ['', ' '], $column);
|
||||
$dynamicKeys[$column] = ucwords($label);
|
||||
}
|
||||
}
|
||||
|
||||
$selectedTpa = $this->request->getGet('tpa');
|
||||
$selectedKey = $this->request->getGet('key');
|
||||
$searchTerm = $this->request->getGet('search');
|
||||
|
||||
// Parse logs with current filters
|
||||
$logEntries = $this->parseLogFileOptimized($filePath, $tpaConfigs, $selectedTpa, $selectedKey, $searchTerm);
|
||||
|
||||
// Date Pagination
|
||||
$prevFile = $nextFile = null;
|
||||
if (preg_match('/log-(\d{4}-\d{2}-\d{2})\.log/', $filename, $match)) {
|
||||
$currentDate = $match[1];
|
||||
$prevD = date('Y-m-d', strtotime('-1 day', strtotime($currentDate)));
|
||||
$nextD = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)));
|
||||
if (file_exists($this->logPath . "log-$prevD.log")) $prevFile = "log-$prevD.log";
|
||||
if (file_exists($this->logPath . "log-$nextD.log")) $nextFile = "log-$nextD.log";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'title' => 'TPA Logs: ' . $filename,
|
||||
'filename' => $filename,
|
||||
'logEntries' => $logEntries,
|
||||
'tpaConfigs' => $tpaConfigs,
|
||||
'dynamicKeys' => $dynamicKeys, // Dynamic Buttons
|
||||
'selectedTpa' => $selectedTpa,
|
||||
'selectedKey' => $selectedKey,
|
||||
'searchTerm' => $searchTerm,
|
||||
'prevFile' => $prevFile,
|
||||
'nextFile' => $nextFile
|
||||
];
|
||||
|
||||
return view('logs/view', $data);
|
||||
}
|
||||
|
||||
private function parseLogFileOptimized($path, $configs, $tpaName, $keyType, $searchTerm)
|
||||
{
|
||||
$entries = [];
|
||||
$handle = fopen($path, 'r');
|
||||
if (!$handle) return [];
|
||||
|
||||
$filters = [];
|
||||
if ($tpaName) {
|
||||
$filters[] = $tpaName;
|
||||
if ($keyType) {
|
||||
foreach ($configs as $conf) {
|
||||
if ($conf['tpa_name'] === $tpaName && isset($conf[$keyType])) {
|
||||
$filters[] = $conf[$keyType];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($searchTerm) $filters[] = $searchTerm;
|
||||
|
||||
$currentEntry = null;
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
if (preg_match('/^(\w+)\s*-\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s*-->\s*(.*)$/', $line, $matches)) {
|
||||
if ($currentEntry && $this->matchesFilters($currentEntry['message'], $filters)) {
|
||||
$entries[] = $currentEntry;
|
||||
}
|
||||
$currentEntry = ['level' => $matches[1], 'date' => $matches[2], 'message' => $matches[3]];
|
||||
} elseif ($currentEntry !== null && trim($line) !== '') {
|
||||
$currentEntry['message'] .= "\n" . $line;
|
||||
}
|
||||
}
|
||||
if ($currentEntry && $this->matchesFilters($currentEntry['message'], $filters)) $entries[] = $currentEntry;
|
||||
|
||||
fclose($handle);
|
||||
return array_reverse($entries);
|
||||
}
|
||||
|
||||
private function matchesFilters($message, $filters)
|
||||
{
|
||||
if (empty($filters)) return false;
|
||||
foreach ($filters as $f) {
|
||||
if (stripos($message, $f) === false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all log files sorted by date (latest first)
|
||||
*/
|
||||
private function getLogFiles()
|
||||
{
|
||||
$files = [];
|
||||
|
||||
if (!is_dir($this->logPath)) {
|
||||
return $files;
|
||||
}
|
||||
|
||||
if (!is_dir($this->logPath)) return $files;
|
||||
$iterator = new \DirectoryIterator($this->logPath);
|
||||
|
||||
foreach ($iterator as $fileInfo) {
|
||||
if ($fileInfo->isFile() && $fileInfo->getExtension() === 'log') {
|
||||
$files[] = [
|
||||
'name' => $fileInfo->getFilename(),
|
||||
'path' => $fileInfo->getPathname(),
|
||||
'size' => $this->formatBytes($fileInfo->getSize()),
|
||||
'modified' => $fileInfo->getMTime(),
|
||||
'modified_date' => date('Y-m-d H:i:s', $fileInfo->getMTime())
|
||||
'size' => round($fileInfo->getSize() / 1024, 2) . ' KB',
|
||||
'modified_date' => date('Y-m-d H:i:s', $fileInfo->getMTime()),
|
||||
'ts' => $fileInfo->getMTime()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by modified time (latest first)
|
||||
usort($files, function($a, $b) {
|
||||
return $b['modified'] - $a['modified'];
|
||||
});
|
||||
|
||||
usort($files, fn($a, $b) => $b['ts'] - $a['ts']);
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* View specific log file content
|
||||
*/
|
||||
public function view($filename = null)
|
||||
{
|
||||
|
||||
|
||||
|
||||
if (!$filename) {
|
||||
return redirect()->to('/logs')->with('error', 'No log file specified');
|
||||
}
|
||||
|
||||
// Security: prevent directory traversal
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->to('/logs')->with('error', 'Log file not found');
|
||||
}
|
||||
|
||||
// ✅ extract date from filename: log-YYYY-MM-DD.log
|
||||
if (preg_match('/log-(\d{4}-\d{2}-\d{2})\.log/', $filename, $match)) {
|
||||
$currentDate = $match[1];
|
||||
|
||||
$prevDate = date('Y-m-d', strtotime('-1 day', strtotime($currentDate)));
|
||||
$nextDate = date('Y-m-d', strtotime('+1 day', strtotime($currentDate)));
|
||||
|
||||
$prevFile = "log-$prevDate.log";
|
||||
$nextFile = "log-$nextDate.log";
|
||||
|
||||
$prevExists = file_exists($this->logPath . $prevFile);
|
||||
$nextExists = file_exists($this->logPath . $nextFile);
|
||||
}
|
||||
|
||||
// Read log file content
|
||||
$content = file_get_contents($filePath);
|
||||
$logEntries = $this->parseLogFile($content);
|
||||
|
||||
$data = [
|
||||
'title' => 'View Log: ' . $filename,
|
||||
'filename' => $filename,
|
||||
'logEntries' => $logEntries,
|
||||
'prevFile' => $prevExists ? $prevFile : null,
|
||||
'nextFile' => $nextExists ? $nextFile : null,
|
||||
'fileSize' => $this->formatBytes(filesize($filePath)),
|
||||
'lastModified' => date('Y-m-d H:i:s', filemtime($filePath))
|
||||
];
|
||||
|
||||
// print_r( $data); die;
|
||||
|
||||
return $this->loadLayout('logs/view', $data);
|
||||
// return view('logs/view', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse log file into structured array
|
||||
*/
|
||||
|
||||
private function parseLogFile($content)
|
||||
{
|
||||
$entries = [];
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
$currentEntry = null;
|
||||
|
||||
// Messages to filter out
|
||||
$skipPatterns = [
|
||||
'/Session: Class initialized using/',
|
||||
'/Session class already loaded/',
|
||||
];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Match CI4 log format: LEVEL - date --> message
|
||||
if (preg_match('/^(\w+)\s*-\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s*-->\s*(.*)$/', $line, $matches)) {
|
||||
|
||||
// Save previous entry if exists (before checking skip)
|
||||
if ($currentEntry !== null) {
|
||||
$entries[] = $currentEntry;
|
||||
$currentEntry = null;
|
||||
}
|
||||
|
||||
// Check if this message should be skipped
|
||||
$shouldSkip = false;
|
||||
foreach ($skipPatterns as $pattern) {
|
||||
if (preg_match($pattern, $matches[3])) {
|
||||
$shouldSkip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldSkip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start new entry
|
||||
$currentEntry = [
|
||||
'level' => $matches[1],
|
||||
'date' => $matches[2],
|
||||
'message' => $matches[3]
|
||||
];
|
||||
} elseif ($currentEntry !== null && trim($line) !== '') {
|
||||
// Continuation of previous message
|
||||
$currentEntry['message'] .= "\n" . $line;
|
||||
}
|
||||
}
|
||||
|
||||
// Add last entry
|
||||
if ($currentEntry !== null) {
|
||||
$entries[] = $currentEntry;
|
||||
}
|
||||
|
||||
return array_reverse($entries); // Latest first
|
||||
}
|
||||
|
||||
/**
|
||||
* Download log file
|
||||
*/
|
||||
public function download($filename = null)
|
||||
{
|
||||
if (!$filename) {
|
||||
return redirect()->to('/logs')->with('error', 'No log file specified');
|
||||
}
|
||||
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->to('/logs')->with('error', 'Log file not found');
|
||||
}
|
||||
|
||||
return $this->response->download($filePath, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete log file
|
||||
*/
|
||||
public function delete($filename = null)
|
||||
{
|
||||
if (!$filename) {
|
||||
return redirect()->to('/logs')->with('error', 'No log file specified');
|
||||
}
|
||||
|
||||
$filename = basename($filename);
|
||||
$filePath = $this->logPath . $filename;
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return redirect()->to('/logs')->with('error', 'Log file not found');
|
||||
}
|
||||
|
||||
if (unlink($filePath)) {
|
||||
return redirect()->to('/logs')->with('success', 'Log file deleted successfully');
|
||||
} else {
|
||||
return redirect()->to('/logs')->with('error', 'Failed to delete log file');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format bytes to human readable format
|
||||
*/
|
||||
private function formatBytes($bytes, $precision = 2)
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all log files
|
||||
*/
|
||||
public function clearAll()
|
||||
{
|
||||
$logFiles = $this->getLogFiles();
|
||||
$deleted = 0;
|
||||
|
||||
foreach ($logFiles as $file) {
|
||||
if (unlink($file['path'])) {
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->to('/logs')->with('success', $deleted . ' log file(s) deleted successfully');
|
||||
}
|
||||
}
|
||||
@ -130,14 +130,14 @@ class MediAssistApiController extends BaseController
|
||||
// ]
|
||||
// ];
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
log_message('error','MEDI_ASSIST - Claim Push | claimId: '.$claimId.' | payload: '.json_encode($body));
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
|
||||
if($response['status'] != true){
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Claim Push FAILED | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
->update([ 'tpa_push_response' => json_encode($response) ]);
|
||||
@ -150,7 +150,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if(!empty($claimRef)){
|
||||
|
||||
log_message('error', 'TPA CLAIM PUSH SUCCESS | claimId: '.$claimId.' | claimReferenceNo: '.$claimRef);
|
||||
log_message('error','MEDI_ASSIST - Claim Push SUCCESS | claimId: '.$claimId.' | claimReferenceNo: '.$claimRef);
|
||||
|
||||
$this->db->table('ticket_master')
|
||||
->where('id',$claimId)
|
||||
@ -159,7 +159,7 @@ class MediAssistApiController extends BaseController
|
||||
return;
|
||||
|
||||
} else {
|
||||
log_message('error', 'TPA CLAIM PUSH API SUCCESS BUT claimReferenceNo EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Claim Push API Failed | claimReferenceNo EMPTY | claimId: '.$claimId.' | response: '.json_encode($response));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -193,17 +193,17 @@ class MediAssistApiController extends BaseController
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
if($response['status'] != true){
|
||||
log_message('error', 'Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Ecard Request FAILED | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
$ecardUrl = $response['data']['ecardUrl'] ?? null;
|
||||
|
||||
if(!empty($ecardUrl)){
|
||||
log_message('error', 'Ecard Request PUSH SUCCESS | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | ecardUrl: '.$ecardUrl);
|
||||
log_message('error','MEDI_ASSIST - Ecard Request PUSH SUCCESS | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | ecardUrl: '.$ecardUrl);
|
||||
return $ecardUrl;
|
||||
} else {
|
||||
log_message('error', 'Ecard Request SUCCESS BUT ecardUrl EMPTY | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Ecard Request SUCCESS BUT ecardUrl EMPTY | employeeId: '.$employeeId.' | policyNo: '.$policyNo.' | response: '.json_encode($response));
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -242,7 +242,7 @@ class MediAssistApiController extends BaseController
|
||||
$client_policy_id = $requestData['client_policy_id'] ?? null;
|
||||
|
||||
if (empty($policyNo)) {
|
||||
log_message('error', 'TPA ID PULL | policy_no missing in request');
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull | policy_no missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'policy_no required'];
|
||||
}else{
|
||||
@ -251,7 +251,7 @@ class MediAssistApiController extends BaseController
|
||||
}
|
||||
|
||||
if (empty($client_policy_id)) {
|
||||
log_message('error', 'TPA ID PULL | client_policy_id missing in request');
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull | client_policy_id missing in request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'client_policy_id required'];
|
||||
}else{
|
||||
@ -259,7 +259,7 @@ class MediAssistApiController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
log_message('error', "TPA ID PULL | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull | called for policy_no: {$policyNo} , client_policy_id: {$client_policy_id}");
|
||||
|
||||
$employeePolicyModel = new EmployeePolicyModel();
|
||||
$employeePolicyData = $employeePolicyModel
|
||||
@ -278,7 +278,7 @@ class MediAssistApiController extends BaseController
|
||||
->findAll();
|
||||
|
||||
if (empty($employeePolicyData)) {
|
||||
log_message('error', 'TPA ID PULL FAILED | employeePolicyData is empty (tpa_id IS NULL from nhance) for this tpa id pull request');
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull FAILED | employeePolicyData is empty (tpa_id IS NULL from nhance) for this TPA ID Pull request');
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'employeePolicyData not found'];
|
||||
}else{
|
||||
@ -301,8 +301,8 @@ class MediAssistApiController extends BaseController
|
||||
"employeeId" => ""
|
||||
];
|
||||
|
||||
log_message('error', "TPA ID PULL | API Request (startIndex={$startIndex}): " . json_encode($body));
|
||||
log_message('error', "TPA ID PULL | API parems " . json_encode([$url, $method, $headers, $body]));
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull | API Request (startIndex={$startIndex}): " . json_encode($body));
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull | API parems " . json_encode([$url, $method, $headers, $body]));
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
@ -312,12 +312,12 @@ class MediAssistApiController extends BaseController
|
||||
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
||||
$file_model = new BatchFileModel();
|
||||
$file_model->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
||||
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
|
||||
log_message('error',"MEDI_ASSIST - Files table status updated for the file id : {$requestData['file_id']}");
|
||||
} else {
|
||||
log_message('error', "Failed to update file table status.");
|
||||
log_message('error',"MEDI_ASSIST - Failed to update file table status.");
|
||||
}
|
||||
|
||||
log_message('error', 'TPA ID PULL API FAILED | API failed: ' . json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - TPA ID Pull API FAILED | API failed: ' . json_encode($response));
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'API call failed', 'data' => $response];
|
||||
@ -329,7 +329,7 @@ class MediAssistApiController extends BaseController
|
||||
$data = $response['data'] ?? [];
|
||||
|
||||
if (!isset($data['benefDetails'])) {
|
||||
log_message('error', "TPA ID PULL FAILED |: 'benefDetails' missing in API response: " . json_encode($data));
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull FAILED |: 'benefDetails' missing in API response: " . json_encode($data));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -337,7 +337,7 @@ class MediAssistApiController extends BaseController
|
||||
$totalCount = $count;
|
||||
$fetchedCount = count($data['benefDetails']);
|
||||
|
||||
log_message('error', "Fetched {$fetchedCount} records (startIndex={$startIndex}) of total {$count}");
|
||||
log_message('error',"MEDI_ASSIST - Fetched {$fetchedCount} records (startIndex={$startIndex}) of total {$count}");
|
||||
|
||||
$allBenef = array_merge($allBenef, $data['benefDetails']);
|
||||
|
||||
@ -376,7 +376,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
$hasMatchForThisPolicy = true;
|
||||
|
||||
// log_message('error', "✅ Match found: emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
// log_message('error',"MEDI_ASSIST - ✅ Match found: emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
|
||||
$sql = "UPDATE employee_polices
|
||||
SET tpa_id = ?
|
||||
@ -390,9 +390,9 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if ($this->db->affectedRows() > 0) {
|
||||
$updated++;
|
||||
log_message('error', "✅ Updated tpa_id={$row['benefMediAssistID']} for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
log_message('error',"MEDI_ASSIST - ✅ Updated tpa_id={$row['benefMediAssistID']} for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
} else {
|
||||
log_message('error', "⚠️ No update (already set or not matched) for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
log_message('error',"MEDI_ASSIST - ⚠️ No update (already set or not matched) for emp_code={$row['priBenefEmpCode']} policy={$row['polNo']}");
|
||||
}
|
||||
|
||||
}
|
||||
@ -423,7 +423,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
// send e-card
|
||||
if(!empty($employee_policy_ids)){
|
||||
log_message('error', "sendMailForDownloadingECard JOB PUSHED.");
|
||||
log_message('error',"MEDI_ASSIST - sendMailForDownloadingECard JOB PUSHED.");
|
||||
Jobs::addJob(['job_name' => 'sendMailForDownloadingECard', 'payload' => ['ids' => $employee_policy_ids, 'client_policy_id' => $client_policy_id]]);
|
||||
}
|
||||
|
||||
@ -432,13 +432,13 @@ class MediAssistApiController extends BaseController
|
||||
$file_model = new BatchFileModel();
|
||||
|
||||
$file_model->where('id', $requestData['file_id'])->set('status', $batch_file_success)->update();
|
||||
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
|
||||
log_message('error',"MEDI_ASSIST - Files table status updated for the file id : {$requestData['file_id']}");
|
||||
} else {
|
||||
log_message('error', "Failed to update file table status.");
|
||||
log_message('error',"MEDI_ASSIST - Failed to update file table status.");
|
||||
}
|
||||
|
||||
|
||||
log_message('error', "TPA ID PULL SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
log_message('error',"MEDI_ASSIST - TPA ID Pull SUCCESS | completed. Total fetched={$totalCount}, updated={$updated}");
|
||||
|
||||
if($function_calling_type == "job"){
|
||||
return [
|
||||
@ -462,9 +462,9 @@ class MediAssistApiController extends BaseController
|
||||
if (isset($requestData['file_id']) && !empty($requestData['file_id'])) {
|
||||
$file_model = new BatchFileModel();
|
||||
$file_model->where('id', $requestData['file_id'])->set('status', 'failed-8')->update();
|
||||
log_message('error', "Files table status updated for the file id : {$requestData['file_id']}");
|
||||
log_message('error',"MEDI_ASSIST - Files table status updated for the file id : {$requestData['file_id']}");
|
||||
} else {
|
||||
log_message('error', "Failed to update file table status.");
|
||||
log_message('error',"MEDI_ASSIST - Failed to update file table status.");
|
||||
}
|
||||
|
||||
$errorData = [
|
||||
@ -478,7 +478,7 @@ class MediAssistApiController extends BaseController
|
||||
'class' => $th->getTrace()[0]['class'] ?? null,
|
||||
];
|
||||
|
||||
log_message('error', 'Exception thrown while calling GetBenefDetails API: ' . json_encode($errorData));
|
||||
log_message('error','MEDI_ASSIST - Exception thrown while calling GetBenefDetails API: ' . json_encode($errorData));
|
||||
if($function_calling_type == "job"){
|
||||
return ['status' => false, 'message' => 'API call failed', 'data' => $errorData];
|
||||
}else{
|
||||
@ -564,15 +564,17 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
|
||||
return ['status' => false,'message' => 'API call failed.','data' => $response ];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
// Extract Claim Status
|
||||
$claimData = $response['data']['claimsData'][0];
|
||||
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||
$tpa_ailments = ($claimData['ailment'] ?? '') . ' - ' . ($claimData['ailmenT_DESC'] ?? '');
|
||||
|
||||
// VALID STATUS LIST
|
||||
$validStatuses = [
|
||||
@ -613,27 +615,35 @@ class MediAssistApiController extends BaseController
|
||||
"DENIAL REVIEW AWAITED" => 66,
|
||||
];
|
||||
|
||||
|
||||
// Maping tpa claim status with local claim Status
|
||||
if (isset($validStatuses[$currentStatus]))
|
||||
{
|
||||
$updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no, 'claim_number' => $tpa_claim_no, 'updated_at' => date('Y-m-d H:i:s')];
|
||||
}else{
|
||||
$updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no, 'claim_number' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'tpa_claim_id' => $tpa_claim_no,
|
||||
'claim_number' => $tpa_claim_no,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
if (isset($validStatuses[$currentStatus])) {
|
||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||
}
|
||||
if (!empty($tpa_claim_type)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
if (!empty($tpa_ailments)) {
|
||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||
}
|
||||
|
||||
|
||||
// UPDATE ticket_master
|
||||
$this->db->table('ticket_master')->where('id', $claimId)->update($updateArray);
|
||||
|
||||
// LOG UPDATE
|
||||
log_message('error', "CLAIM STATUS SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
||||
log_message('error',"MEDI_ASSIST - Claim Status SUCCESS | Updated ticket ID $claimId with Claim Status: $currentStatus");
|
||||
|
||||
return ['status' => true,'message' => 'Claim status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
||||
return ['status' => true,'message' => 'Claim Status updated.','updated_status' => $currentStatus,'api_response' => $response];
|
||||
}
|
||||
|
||||
public function IRSubmission($claimId = null) // 585 this id for test
|
||||
{
|
||||
log_message('error', "IRSubmission INIT for ticket_id={$claimId}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission | INIT for ticket_id={$claimId}");
|
||||
|
||||
// 1. FETCH TICKET DETAILS
|
||||
$ticket = $this->db->table('ticket_master tm')
|
||||
@ -654,7 +664,7 @@ class MediAssistApiController extends BaseController
|
||||
->getRowArray();
|
||||
|
||||
if (!$ticket || empty($ticket['ClaimID'])) {
|
||||
log_message('error', "IRSubmission FAILED → ClaimID NOT FOUND for ticket_id={$claimId}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission FAILED → ClaimID NOT FOUND for ticket_id={$claimId}");
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
@ -683,17 +693,17 @@ class MediAssistApiController extends BaseController
|
||||
$downloadUrl = base_url('fileDownload?file_path=') . $fileDir;
|
||||
} else {
|
||||
$downloadUrl = "";
|
||||
log_message('error', "File NOT FOUND on server → {$fileDir}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission File NOT FOUND on server → {$fileDir}");
|
||||
}
|
||||
|
||||
log_message('error', "IRSubmission Attachment Ready: {$filename} | URL={$downloadUrl}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission Attachment Ready: {$filename} | URL={$downloadUrl}");
|
||||
|
||||
$Attachments[] = [
|
||||
"AttachmentName" => $filename,
|
||||
"AttachmentPath" => $downloadUrl
|
||||
];
|
||||
} else {
|
||||
log_message('error', "IRSubmission Missing File URL → file_id={$file['id']}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission Missing File URL → file_id={$file['id']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -704,7 +714,7 @@ class MediAssistApiController extends BaseController
|
||||
"Attachments" => $Attachments
|
||||
];
|
||||
|
||||
log_message('error', "IRSubmission Request Body => " . json_encode($body));
|
||||
log_message('error',"MEDI_ASSIST - IR Submission Request Body => " . json_encode($body));
|
||||
|
||||
// 4. SEND API CALL
|
||||
helper('api');
|
||||
@ -720,13 +730,13 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
log_message('error', "IRSubmission API Response => " . json_encode($response));
|
||||
log_message('error',"MEDI_ASSIST - IR Submission API Response => " . json_encode($response));
|
||||
|
||||
// 5. HANDLE RESPONSE
|
||||
if (!$response['status']) {
|
||||
log_message(
|
||||
'error',
|
||||
"IRSubmission FAILED for ClaimID={$ticket['ClaimID']} → Response=" . json_encode($response)
|
||||
"MEDI_ASSIST - IR Submission FAILED for ClaimID={$ticket['ClaimID']} → Response=" . json_encode($response)
|
||||
);
|
||||
|
||||
return [
|
||||
@ -736,7 +746,7 @@ class MediAssistApiController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
log_message('error', "IRSubmission SUCCESS → ClaimID={$ticket['ClaimID']}");
|
||||
log_message('error',"MEDI_ASSIST - IR Submission SUCCESS → ClaimID={$ticket['ClaimID']}");
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
@ -750,7 +760,7 @@ class MediAssistApiController extends BaseController
|
||||
$file_id = $array['file_id'];
|
||||
$json = file_get_contents($array['json_file_path']);
|
||||
$records = json_decode($json, true);
|
||||
// log_message('error','saveMediAssitAPIData' . json_encode($array));//die();
|
||||
// log_message('error','MEDI_ASSIST - saveMediAssitAPIData' . json_encode($array));//die();
|
||||
$file_model = new BatchFileModel();
|
||||
$file_info = $file_model->where('id', $file_id)->find();
|
||||
// dd($file_info);
|
||||
@ -787,7 +797,7 @@ class MediAssistApiController extends BaseController
|
||||
'created_by' => $file_info[0]['created_by'] ?? null,
|
||||
];
|
||||
}
|
||||
// log_message('error','COUNT' . count($mappedRows));
|
||||
// log_message('error','MEDI_ASSIST - COUNT' . count($mappedRows));
|
||||
// print_rr($mappedRows);//die();
|
||||
$result = $tpaApiDataModel->insertBatchWithChunkLog($mappedRows,500,('FILE_ID_'.$file_id));
|
||||
// unlink($file_array['json_file_path']); // delete temp json file
|
||||
@ -829,7 +839,7 @@ class MediAssistApiController extends BaseController
|
||||
// dd($TicketData);
|
||||
|
||||
if (!$TicketData) {
|
||||
log_message('error', "Claims not found to update status");
|
||||
log_message('error',"MEDI_ASSIST - Claims not found to update Claim Status");
|
||||
return $this->response->setJSON(['status' => false,'message' => 'Claims not found' ]);
|
||||
}
|
||||
|
||||
@ -871,14 +881,16 @@ class MediAssistApiController extends BaseController
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
log_message('error','MEDI_ASSIST - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
$error_data[$claimId][['status' => false,'message' => 'API call failed.','data' => $response]];
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
// Extract Claim Status
|
||||
$claimData = $response['data']['claimsData'][0];
|
||||
$currentStatus = $claimData['claim_Current_Status'] ?? '';
|
||||
$tpa_claim_no = $claimData['tpA_CLAIM_NO'] ?? '';
|
||||
$tpa_claim_type = $claimData['typE_OF_CLAIM'] ?? '';
|
||||
$tpa_ailments = ($claimData['ailment'] ?? '') . ' - ' . ($claimData['ailmenT_DESC'] ?? '');
|
||||
|
||||
// VALID STATUS LIST
|
||||
$validStatuses = [
|
||||
@ -920,12 +932,20 @@ class MediAssistApiController extends BaseController
|
||||
"DENIAL REVIEW AWAITED" => 66,
|
||||
];
|
||||
|
||||
// Maping tpa claim status with local claim Status
|
||||
if (isset($validStatuses[$currentStatus]))
|
||||
{
|
||||
$updateArray = ['claim_status_id' => $validStatuses[$currentStatus] , 'tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'claim_number' => $tpa_claim_no , 'updated_at' => date('Y-m-d H:i:s')];
|
||||
}else{
|
||||
$updateArray = ['tpa_claim_status' => $currentStatus , 'tpa_claim_id' => $tpa_claim_no , 'claim_number' => $tpa_claim_no, 'updated_at' => date('Y-m-d H:i:s')];
|
||||
$updateArray = [
|
||||
'tpa_claim_status' => $currentStatus,
|
||||
'tpa_claim_id' => $tpa_claim_no,
|
||||
'claim_number' => $tpa_claim_no,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
if (isset($validStatuses[$currentStatus])) {
|
||||
$updateArray['claim_status_id'] = $validStatuses[$currentStatus];
|
||||
}
|
||||
if (!empty($tpa_claim_type)) {
|
||||
$updateArray['tpa_claim_type'] = $tpa_claim_type;
|
||||
}
|
||||
if (!empty($tpa_ailments)) {
|
||||
$updateArray['tpa_ailments'] = $tpa_ailments;
|
||||
}
|
||||
|
||||
// UPDATE ticket_master
|
||||
@ -933,13 +953,13 @@ class MediAssistApiController extends BaseController
|
||||
$status_updated_count ++;
|
||||
|
||||
// LOG UPDATE
|
||||
log_message('error', "CLAIM STATUS SUCCESS | Updated ticket ID $claimId with claim status: $currentStatus");
|
||||
log_message('error',"MEDI_ASSIST - Claim Status SUCCESS | Updated ticket ID $claimId with Claim Status: $currentStatus");
|
||||
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => true,
|
||||
'message' => 'Claim status updated.',
|
||||
'message' => 'Claim Status updated.',
|
||||
'updated_status' => $currentStatus,
|
||||
'api_response' => $response,
|
||||
'count' => $status_updated_count,
|
||||
@ -1002,7 +1022,7 @@ class MediAssistApiController extends BaseController
|
||||
->getResultArray();
|
||||
|
||||
if (empty($policies)) {
|
||||
log_message('error', 'No policies found for claim sync');
|
||||
log_message('error','MEDI_ASSIST - Sync TPA Claims | No policies found for claim sync');
|
||||
return $this->response->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Policies not found'
|
||||
@ -1067,7 +1087,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if (empty($response['status']) || empty($response['data']['claimsData'])) {
|
||||
|
||||
log_message('error','CLAIM STATUS FAILED | ' .'policyNo: ' . $policy['policyNo'] .' | ' . $chunkStart->format('Y-m-d') .' to ' . $chunkEnd->format('Y-m-d') .' | response: ' . json_encode($response) );
|
||||
log_message('error','MEDI_ASSIST - Sync TPA Claims | ' .'policyNo: ' . $policy['policyNo'] .' | ' . $chunkStart->format('Y-m-d') .' to ' . $chunkEnd->format('Y-m-d') .' | response: ' . json_encode($response) );
|
||||
|
||||
$errorData[] = [
|
||||
'policy_no' => $policy['policyNo'],
|
||||
@ -1153,7 +1173,7 @@ class MediAssistApiController extends BaseController
|
||||
$claimStatusId = $validStatuses[$currentStatus] ?? null;
|
||||
|
||||
if (!$claimStatusId) {
|
||||
log_message('error', 'Unknown claim status: '.$currentStatus);
|
||||
log_message('error','MEDI_ASSIST - Sync TPA Claims | Unknown Claim Status: '.$currentStatus);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1243,13 +1263,18 @@ class MediAssistApiController extends BaseController
|
||||
// Payment
|
||||
'utr_details' => $value['banK_CHEQUE_NO'] ?? null,
|
||||
'settle_letter' => $value['settlement_LetterLink'] ?? null,
|
||||
|
||||
//others
|
||||
'tpa_claim_type' => $value['typE_OF_CLAIM'],
|
||||
'tpa_ailments' => ($value['ailment'] ?? '') . ' - ' . ($value['ailmenT_DESC'] ?? ''),
|
||||
|
||||
];
|
||||
|
||||
$this->db->table('ticket_master')->insert($claimData);
|
||||
|
||||
log_message(
|
||||
'error',
|
||||
'New claim created | Policy: '.$claimData['policy_no'].' | Claim: '.$claimData['claim_number']
|
||||
'MEDI_ASSIST - Sync TPA Claims | New claim created | Policy: '.$claimData['policy_no'].' | Claim: '.$claimData['claim_number']
|
||||
);
|
||||
|
||||
}
|
||||
@ -1259,7 +1284,7 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => true,
|
||||
'message' => 'TPA claim status sync completed',
|
||||
'message' => 'TPA Claim Status sync completed',
|
||||
'total_records' => count($finalResult),
|
||||
'result' => $finalResult,
|
||||
'errors' => $errorData
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Api;
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\SalesActualLeadModel;
|
||||
@ -27,6 +27,73 @@ 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();
|
||||
|
||||
$this->loadLayout('sales/tracker_view', $data);
|
||||
}
|
||||
|
||||
public function completeActivity($id) {
|
||||
try {
|
||||
$data = $this->request->getJSON(true);
|
||||
$data['updated_by'] = $this->getUserId();
|
||||
|
||||
// 1. Mark current activity as completed
|
||||
$this->activityModel->completeActivity((int)$id, [
|
||||
'completion_notes' => $data['completion_notes'],
|
||||
'updated_by' => $data['updated_by']
|
||||
]);
|
||||
|
||||
// 2. Handle follow-up if requested
|
||||
if (!empty($data['schedule_followup']) && $data['schedule_followup'] === 'yes') {
|
||||
$activity = $this->activityModel->find($id);
|
||||
$this->activityModel->insert([
|
||||
'lead_id' => $activity['lead_id'],
|
||||
'activity_type' => $data['followup_type'],
|
||||
'notes' => $data['followup_notes'],
|
||||
'scheduled_date' => $data['followup_schedule'],
|
||||
'assigned_to' => $activity['assigned_to'],
|
||||
'status' => 'pending',
|
||||
'created_by' => $this->getUserId()
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->respond(['status' => 'success', 'message' => 'Activity updated']);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Corrected createLead to handle assigned_to as ID
|
||||
*/
|
||||
public function createLead()
|
||||
{
|
||||
try {
|
||||
$data = $this->request->getJSON(true);
|
||||
$data['created_by'] = $this->getUserId();
|
||||
|
||||
// Ensure assigned_to is a valid integer from user_profiles
|
||||
if (empty($data['assigned_to'])) {
|
||||
return $this->fail('Please assign this lead to a user.');
|
||||
}
|
||||
|
||||
if (!$this->leadModel->insert($data)) {
|
||||
return $this->fail($this->leadModel->errors());
|
||||
}
|
||||
|
||||
return $this->respondCreated(['status' => 'success', 'id' => $this->leadModel->getInsertID()]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== LEAD APIs ====================
|
||||
|
||||
/**
|
||||
@ -81,46 +148,6 @@ class SalesController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new lead
|
||||
* POST /api/sales/leads
|
||||
*/
|
||||
public function createLead()
|
||||
{
|
||||
try {
|
||||
$data = $this->request->getJSON(true);
|
||||
|
||||
// Set created_by and updated_by from authenticated user
|
||||
$data['created_by'] = $this->getUserId();
|
||||
$data['updated_by'] = $this->getUserId();
|
||||
|
||||
if (!$this->leadModel->insert($data)) {
|
||||
return $this->fail($this->leadModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$leadId = $this->leadModel->getInsertID();
|
||||
|
||||
// Insert contact persons if provided
|
||||
if (!empty($data['contact_persons'])) {
|
||||
foreach ($data['contact_persons'] as $contact) {
|
||||
$contact['lead_id'] = $leadId;
|
||||
$contact['created_by'] = $this->getUserId();
|
||||
$this->contactModel->insert($contact);
|
||||
}
|
||||
}
|
||||
|
||||
$lead = $this->leadModel->getLeadComplete($leadId);
|
||||
|
||||
return $this->respondCreated([
|
||||
'status' => 'success',
|
||||
'message' => 'Lead created successfully',
|
||||
'data' => $lead
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update lead
|
||||
* PUT /api/sales/leads/{id}
|
||||
@ -467,55 +494,6 @@ class SalesController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete activity
|
||||
* POST /api/sales/activities/{id}/complete
|
||||
*/
|
||||
public function completeActivity($id)
|
||||
{
|
||||
try {
|
||||
$activity = $this->activityModel->find((int)$id);
|
||||
|
||||
if (!$activity) {
|
||||
return $this->failNotFound('Activity not found');
|
||||
}
|
||||
|
||||
if ($activity['status'] === 'completed') {
|
||||
return $this->fail('Activity is already completed', ResponseInterface::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$data = $this->request->getJSON(true);
|
||||
$data['updated_by'] = $this->getUserId();
|
||||
|
||||
$this->activityModel->completeActivity((int)$id, $data);
|
||||
|
||||
// Create follow-up activity if requested
|
||||
if (!empty($data['create_followup']) && $data['create_followup'] === true) {
|
||||
$followupData = [
|
||||
'lead_id' => $activity['lead_id'],
|
||||
'activity_type' => $data['followup_type'] ?? 'Call',
|
||||
'notes' => $data['followup_notes'] ?? '',
|
||||
'scheduled_date' => $data['followup_date'] ?? null,
|
||||
'assigned_to' => $activity['assigned_to'],
|
||||
'parent_activity_id' => $id,
|
||||
'created_by' => $this->getUserId(),
|
||||
'updated_by' => $this->getUserId(),
|
||||
];
|
||||
|
||||
$this->activityModel->insert($followupData);
|
||||
}
|
||||
|
||||
$updatedActivity = $this->activityModel->find((int)$id);
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'message' => 'Activity completed successfully',
|
||||
'data' => $updatedActivity
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete activity
|
||||
|
||||
@ -412,7 +412,7 @@ class VidalApiController extends BaseController
|
||||
return ['status' => 'success','data' => $decryptedData["redirectUrl"]];
|
||||
}
|
||||
|
||||
public function ClaimDetail($claimId = null) //234
|
||||
public function ClaimDetail($claimId = 234) //234
|
||||
{
|
||||
helper('api');
|
||||
|
||||
@ -454,16 +454,24 @@ class VidalApiController extends BaseController
|
||||
$body = [
|
||||
'empNO' => "",
|
||||
'tpaCardID' => "",
|
||||
'claimID' => $ticket['claimID'],
|
||||
'claimID' => "CHE-0226-CL-0013404", //$ticket['claimID'],
|
||||
'emailID' => "",
|
||||
'mobileNO' => "",
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
$body = [
|
||||
'empNO' => "",
|
||||
'tpaCardID' => "",
|
||||
'claimID' => "CHE-0226-CL-0013893", //$ticket['claimID'],
|
||||
'emailID' => "",
|
||||
'mobileNO' => "",
|
||||
];
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
// dd($response);
|
||||
dd($response);
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['data']['claims'][0])) {
|
||||
log_message('error', 'CLAIM STATUS FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
|
||||
@ -60,52 +60,52 @@ class SalesActivityModel extends Model
|
||||
protected $skipValidation = false;
|
||||
|
||||
/**
|
||||
* Get activities by lead with user details
|
||||
* Get sales_activities by lead with user details
|
||||
*/
|
||||
public function getActivitiesByLead($leadId, $status = null)
|
||||
{
|
||||
$builder = $this->select('activities.*, user_profiles.username as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left')
|
||||
->where('activities.lead_id', $leadId);
|
||||
$builder = $this->select('sales_activities.*, user_profiles.first_name as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left')
|
||||
->where('sales_activities.lead_id', $leadId);
|
||||
|
||||
if ($status) {
|
||||
$builder->where('activities.status', $status);
|
||||
$builder->where('sales_activities.status', $status);
|
||||
}
|
||||
|
||||
return $builder->orderBy('activities.scheduled_date', 'DESC')->findAll();
|
||||
return $builder->orderBy('sales_activities.scheduled_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all activities with filters
|
||||
* Get all sales_activities with filters
|
||||
*/
|
||||
public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0)
|
||||
{
|
||||
$builder = $this->select('activities.*, actual_leads.company_name, user_profiles.username as assigned_to_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = activities.lead_id', 'left')
|
||||
->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left');
|
||||
$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')
|
||||
->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left');
|
||||
|
||||
if (!empty($filters['status'])) {
|
||||
$builder->where('activities.status', $filters['status']);
|
||||
$builder->where('sales_activities.status', $filters['status']);
|
||||
}
|
||||
|
||||
if (!empty($filters['activity_type'])) {
|
||||
$builder->where('activities.activity_type', $filters['activity_type']);
|
||||
$builder->where('sales_activities.activity_type', $filters['activity_type']);
|
||||
}
|
||||
|
||||
if (!empty($filters['assigned_to'])) {
|
||||
$builder->where('activities.assigned_to', $filters['assigned_to']);
|
||||
$builder->where('sales_activities.assigned_to', $filters['assigned_to']);
|
||||
}
|
||||
|
||||
if (!empty($filters['date_from'])) {
|
||||
$builder->where('activities.scheduled_date >=', $filters['date_from']);
|
||||
$builder->where('sales_activities.scheduled_date >=', $filters['date_from']);
|
||||
}
|
||||
|
||||
if (!empty($filters['date_to'])) {
|
||||
$builder->where('activities.scheduled_date <=', $filters['date_to']);
|
||||
$builder->where('sales_activities.scheduled_date <=', $filters['date_to']);
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => $builder->orderBy('activities.scheduled_date', 'DESC')
|
||||
'data' => $builder->orderBy('sales_activities.scheduled_date', 'DESC')
|
||||
->limit($limit, $offset)->findAll(),
|
||||
'total' => $builder->countAllResults(false)
|
||||
];
|
||||
@ -125,7 +125,7 @@ class SalesActivityModel extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending activities count by user
|
||||
* Get pending sales_activities count by user
|
||||
*/
|
||||
public function getPendingActivitiesCount($userId)
|
||||
{
|
||||
@ -136,18 +136,18 @@ class SalesActivityModel extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upcoming activities for a user
|
||||
* Get upcoming sales_activities for a user
|
||||
*/
|
||||
public function getUpcomingActivities($userId, $days = 7, $limit = 10)
|
||||
{
|
||||
$endDate = date('Y-m-d H:i:s', strtotime("+{$days} days"));
|
||||
|
||||
return $this->select('activities.*, actual_leads.company_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = activities.lead_id', 'left')
|
||||
->where('activities.assigned_to', $userId)
|
||||
->where('activities.status', 'pending')
|
||||
->where('activities.scheduled_date <=', $endDate)
|
||||
->orderBy('activities.scheduled_date', 'ASC')
|
||||
return $this->select('sales_activities.*, actual_leads.company_name')
|
||||
->join('actual_leads', '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)
|
||||
->orderBy('sales_activities.scheduled_date', 'ASC')
|
||||
->limit($limit)
|
||||
->findAll();
|
||||
}
|
||||
@ -157,10 +157,10 @@ class SalesActivityModel extends Model
|
||||
*/
|
||||
public function getActivityTimeline($leadId)
|
||||
{
|
||||
return $this->select('activities.*, user_profiles.username as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left')
|
||||
->where('activities.lead_id', $leadId)
|
||||
->orderBy('activities.scheduled_date', 'DESC')
|
||||
return $this->select('sales_activities.*, user_profiles.first_name as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left')
|
||||
->where('sales_activities.lead_id', $leadId)
|
||||
->orderBy('sales_activities.scheduled_date', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* Lead Model
|
||||
* Handles all operations related to actual_leads table
|
||||
* Handles all operations related to sales_actual_leads table
|
||||
*/
|
||||
class SalesActualLeadModel extends Model
|
||||
{
|
||||
@ -63,9 +63,9 @@ class SalesActualLeadModel extends Model
|
||||
*/
|
||||
public function getLeadWithUser($leadId)
|
||||
{
|
||||
return $this->select('actual_leads.*, user_profiles.username as assigned_to_name, user_profiles.email as assigned_to_email')
|
||||
->join('user_profiles', 'user_profiles.id = actual_leads.assigned_to', 'left')
|
||||
->where('actual_leads.lead_id', $leadId)
|
||||
return $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')
|
||||
->where('sales_actual_leads.lead_id', $leadId)
|
||||
->first();
|
||||
}
|
||||
|
||||
@ -74,22 +74,22 @@ class SalesActualLeadModel extends Model
|
||||
*/
|
||||
public function getLeadsWithFilters($filters = [], $limit = 10, $offset = 0)
|
||||
{
|
||||
$builder = $this->select('actual_leads.*, user_profiles.username as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = actual_leads.assigned_to', 'left');
|
||||
$builder = $this->select('sales_actual_leads.*, user_profiles.first_name as assigned_to_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_actual_leads.assigned_to', 'left');
|
||||
|
||||
if (!empty($filters['status'])) {
|
||||
$builder->where('actual_leads.status', $filters['status']);
|
||||
$builder->where('sales_actual_leads.status', $filters['status']);
|
||||
}
|
||||
|
||||
if (!empty($filters['assigned_to'])) {
|
||||
$builder->where('actual_leads.assigned_to', $filters['assigned_to']);
|
||||
$builder->where('sales_actual_leads.assigned_to', $filters['assigned_to']);
|
||||
}
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$builder->groupStart()
|
||||
->like('actual_leads.company_name', $filters['search'])
|
||||
->orLike('actual_leads.email', $filters['search'])
|
||||
->orLike('actual_leads.phone', $filters['search'])
|
||||
->like('sales_actual_leads.company_name', $filters['search'])
|
||||
->orLike('sales_actual_leads.email', $filters['search'])
|
||||
->orLike('sales_actual_leads.phone', $filters['search'])
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
|
||||
@ -53,10 +53,10 @@ class SalesLeadNoteModel extends Model
|
||||
*/
|
||||
public function getNotesByLead($leadId)
|
||||
{
|
||||
return $this->select('lead_notes.*, user_profiles.username')
|
||||
->join('user_profiles', 'user_profiles.id = lead_notes.user_id', 'left')
|
||||
->where('lead_notes.lead_id', $leadId)
|
||||
->orderBy('lead_notes.created_at', 'DESC')
|
||||
return $this->select('sales_lead_notes.*, user_profiles.first_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_lead_notes.created_by', 'left')
|
||||
->where('sales_lead_notes.lead_id', $leadId)
|
||||
->orderBy('sales_lead_notes.created_at', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
@ -65,10 +65,10 @@ class SalesLeadNoteModel extends Model
|
||||
*/
|
||||
public function getNotesByUser($userId, $limit = 20, $offset = 0)
|
||||
{
|
||||
return $this->select('lead_notes.*, actual_leads.company_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = lead_notes.lead_id', 'left')
|
||||
->where('lead_notes.user_id', $userId)
|
||||
->orderBy('lead_notes.created_at', 'DESC')
|
||||
return $this->select('sales_lead_notes.*, actual_leads.company_name')
|
||||
->join('actual_leads', 'actual_leads.lead_id = sales_lead_notes.lead_id', 'left')
|
||||
->where('sales_lead_notes.created_by', $userId)
|
||||
->orderBy('sales_lead_notes.created_at', 'DESC')
|
||||
->limit($limit, $offset)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
12
app/Models/TpaConfigModel.php
Normal file
12
app/Models/TpaConfigModel.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class TpaConfigModel extends Model {
|
||||
protected $table = 'tpa_log_config';
|
||||
protected $returnType = 'array';
|
||||
}
|
||||
|
||||
?>
|
||||
@ -2,482 +2,126 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= esc($title) ?></title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
:root {
|
||||
--primary: #6366f1;
|
||||
--dark: #1e293b;
|
||||
--bg: #f8fafc;
|
||||
}
|
||||
body { font-family: 'Inter', system-ui, sans-serif; background: var(--bg); padding: 20px; color: var(--dark); }
|
||||
.container { max-width: 1300px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); overflow: hidden; }
|
||||
|
||||
/* Header & Navigation */
|
||||
.header { background: #fff; padding: 20px; border-bottom: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; }
|
||||
.filter-section { padding: 20px; background: #fff; border-bottom: 1px solid #e2e8f0; }
|
||||
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 15px; align-items: center; }
|
||||
|
||||
.btn { padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 13px; font-weight: 500; border: 1px solid #e2e8f0; background: #fff; color: #64748b; transition: all 0.2s; }
|
||||
.btn:hover { background: #f1f5f9; border-color: #cbd5e1; }
|
||||
.btn.active { background: var(--primary); color: white; border-color: var(--primary); }
|
||||
|
||||
/* body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
} */
|
||||
/* Search Box */
|
||||
.search-container { position: relative; margin-top: 10px; }
|
||||
.search-input { width: 100%; padding: 12px 15px; border-radius: 8px; border: 1px solid #e2e8f0; background: #f8fafc; outline: none; font-size: 14px; }
|
||||
.search-input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); }
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
/* background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); */
|
||||
color: black;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
margin-top: 15px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.file-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 8px 16px;
|
||||
border: 2px solid #dee2e6;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
border-color: #667eea;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 10px 15px;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
width: 300px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.search-box:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.log-entries {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
background: white;
|
||||
border-left: 4px solid #6c757d;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.log-entry:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.log-entry.critical {
|
||||
border-left-color: #dc3545;
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.log-entry.error {
|
||||
border-left-color: #fd7e14;
|
||||
background: #fff8f5;
|
||||
}
|
||||
|
||||
.log-entry.warning {
|
||||
border-left-color: #ffc107;
|
||||
background: #fffef5;
|
||||
}
|
||||
|
||||
.log-entry.info {
|
||||
border-left-color: #17a2b8;
|
||||
background: #f5fcfd;
|
||||
}
|
||||
|
||||
.log-entry.debug {
|
||||
border-left-color: #6c757d;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.level-critical {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.level-error {
|
||||
background: #fd7e14;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.level-warning {
|
||||
background: #ffc107;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.level-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.level-debug {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.log-date {
|
||||
color: #6c757d;
|
||||
font-size: 13px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.no-logs {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
padding: 0px;
|
||||
border: 1px solid #007bff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
border-width: 1px;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-card p {
|
||||
opacity: 0.9;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.search-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
/* Log Console View */
|
||||
.log-viewport { background: #0f172a; padding: 20px; max-height: 650px; overflow-y: auto; }
|
||||
.log-card { margin-bottom: 12px; padding: 15px; border-radius: 8px; font-family: 'Fira Code', 'Courier New', monospace; font-size: 13px; border-left: 4px solid #475569; position: relative; background: #1e293b; }
|
||||
|
||||
.log-meta { display: flex; justify-content: space-between; margin-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 5px; }
|
||||
.log-date { color: #94a3b8; font-size: 12px; }
|
||||
.log-level { font-weight: 700; text-transform: uppercase; font-size: 11px; padding: 2px 8px; border-radius: 4px; }
|
||||
|
||||
.level-critical { border-left-color: #ef4444; } .level-critical .log-level { background: #ef4444; color: #fff; }
|
||||
.level-error { border-left-color: #f87171; } .level-error .log-level { background: #f87171; color: #fff; }
|
||||
.level-info { border-left-color: #10b981; } .level-info .log-level { background: #10b981; color: #fff; }
|
||||
.level-warning { border-left-color: #f59e0b; } .level-warning .log-level { background: #f59e0b; color: #fff; }
|
||||
|
||||
.log-msg { color: #e2e8f0; line-height: 1.6; white-space: pre-wrap; word-break: break-all; }
|
||||
|
||||
.empty-state { text-align: center; color: #94a3b8; padding: 40px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h1>
|
||||
📄 <?= esc($filename) ?>
|
||||
</h1>
|
||||
|
||||
<div style="display:flex; gap:10px; margin-bottom:15px;">
|
||||
<?php if ($prevFile): ?>
|
||||
<a href="<?= base_url('logs/view/' . $prevFile) ?>" class="btn btn-primary">Previous</a>
|
||||
<?php else: ?>
|
||||
<button class="btn btn-secondary" disabled>⬅ Previous Day</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($nextFile): ?>
|
||||
<a href="<?= base_url('logs/view/' . $nextFile) ?>" class="btn btn-primary">Next</a>
|
||||
<?php else: ?>
|
||||
<button class="btn btn-secondary" disabled>Next Day ➡</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div class="file-info">
|
||||
<div class="file-info-item">
|
||||
<strong>Size:</strong> <?= esc($fileSize) ?>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<strong>Last Modified:</strong> <?= esc($lastModified) ?>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<strong>Total Entries:</strong> <?= count($logEntries) ?>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="content">
|
||||
<div class="stats">
|
||||
|
||||
|
||||
|
||||
<!--<div class="stat-card">
|
||||
<h3 id="total-count"><?= count($logEntries) ?></h3>
|
||||
<p>Total Entries</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="critical-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => strtoupper($e['level']) === 'CRITICAL')) ?>
|
||||
</h3>
|
||||
<p>Critical</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="error-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => strtoupper($e['level']) === 'ERROR')) ?>
|
||||
</h3>
|
||||
<p>Errors</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="warning-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => strtoupper($e['level']) === 'WARNING')) ?>
|
||||
</h3>
|
||||
<p>Warnings</p>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA CLAIM PUSH SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim success</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA CLAIM PUSH FAILED') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim failed</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA ID PULL SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Tpa no pull success</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'TPA ID PULL FAILED') !== false)) ?>
|
||||
</h3>
|
||||
<p>Tpa no pull Failed</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'CLAIM STATUS SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim status fetch success</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'CLAIM STATUS FAILED') !== false)) ?>
|
||||
</h3>
|
||||
<p>Claim status fetch failed</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newtoken-count">
|
||||
<?= count(array_filter($logEntries, fn($e) => stripos($e['message'], 'Ecard Request PUSH SUCCESS') !== false)) ?>
|
||||
</h3>
|
||||
<p>Ecard Request</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="filter-group">
|
||||
<strong>Filter:</strong>
|
||||
<button class="filter-btn active" data-level="all">All</button>
|
||||
<button class="filter-btn" data-level="critical">Critical</button>
|
||||
<button class="filter-btn" data-level="error">Error</button>
|
||||
<button class="filter-btn" data-level="warning">Warning</button>
|
||||
<button class="filter-btn" data-level="info">Info</button>
|
||||
<button class="filter-btn" data-level="debug">Debug</button>
|
||||
</div>
|
||||
<input type="text" class="search-box" id="searchBox" placeholder="🔍 Search log messages...">
|
||||
</div>
|
||||
|
||||
<?php if (empty($logEntries)): ?>
|
||||
<div class="no-logs">
|
||||
<h3>No Log Entries Found</h3>
|
||||
<p>This log file is empty or couldn't be parsed.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="log-entries" id="logEntries">
|
||||
<?php foreach ($logEntries as $entry): ?>
|
||||
<?php
|
||||
$level = strtolower($entry['level']);
|
||||
$levelClass = 'level-' . $level;
|
||||
?>
|
||||
<div class="log-entry <?= $level ?>" data-level="<?= $level ?>">
|
||||
<div class="log-header">
|
||||
<span class="log-level <?= $levelClass ?>">
|
||||
<?= esc(strtoupper($entry['level'])) ?>
|
||||
</span>
|
||||
<span class="log-date"><?= esc($entry['date']) ?></span>
|
||||
</div>
|
||||
<div class="log-message"><?= esc($entry['message']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h2 style="font-size: 1.25rem;">📄 <?= esc($filename) ?></h2>
|
||||
<div class="btn-group">
|
||||
<a href="<?= base_url('logs/view/'.$prevFile."?tpa=$selectedTpa&key=$selectedKey") ?>" class="btn <?= !$prevFile ? 'disabled' : '' ?>">← Previous Day</a>
|
||||
<a href="<?= base_url('logs/view/'.$nextFile."?tpa=$selectedTpa&key=$selectedKey") ?>" class="btn <?= !$nextFile ? 'disabled' : '' ?>">Next Day →</a>
|
||||
<a href="<?= base_url('logs') ?>" class="btn">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Filter functionality
|
||||
const filterBtns = document.querySelectorAll('.filter-btn');
|
||||
const logEntries = document.querySelectorAll('.log-entry');
|
||||
const searchBox = document.getElementById('searchBox');
|
||||
<div class="filter-section">
|
||||
<div class="btn-group">
|
||||
<span style="font-weight: 600; min-width: 80px;">Select TPA:</span>
|
||||
<?php foreach($tpaConfigs as $conf): ?>
|
||||
<a href="<?= base_url("logs/view/$filename?tpa=".$conf['tpa_name']) ?>"
|
||||
class="btn <?= $selectedTpa == $conf['tpa_name'] ? 'active' : '' ?>">
|
||||
<?= esc($conf['tpa_name']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<a href="<?= base_url("logs/view/$filename") ?>" class="btn" style="color: #ef4444;">Clear Filters</a>
|
||||
</div>
|
||||
|
||||
let currentFilter = 'all';
|
||||
<?php if($selectedTpa): ?>
|
||||
<div class="btn-group">
|
||||
<span style="font-weight: 600; min-width: 80px;">Action:</span>
|
||||
<?php foreach($dynamicKeys as $columnName => $label): ?>
|
||||
<a href="<?= base_url("logs/view/$filename?tpa=$selectedTpa&key=$columnName") ?>"
|
||||
class="btn <?= $selectedKey == $columnName ? 'active' : '' ?>">
|
||||
<?= esc($label) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
filterBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
filterBtns.forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentFilter = btn.dataset.level;
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
<form action="<?= base_url("logs/view/$filename") ?>" method="get" class="search-container" style="margin-top: 15px;">
|
||||
<input type="hidden" name="tpa" value="<?= esc($selectedTpa) ?>">
|
||||
<input type="hidden" name="key" value="<?= esc($selectedKey) ?>">
|
||||
<input type="text" name="search" class="search-input"
|
||||
placeholder="🔍 Search in filtered logs..." value="<?= esc($searchTerm) ?>">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
searchBox.addEventListener('input', applyFilters);
|
||||
|
||||
function applyFilters() {
|
||||
const searchTerm = searchBox.value.toLowerCase();
|
||||
|
||||
logEntries.forEach(entry => {
|
||||
const level = entry.dataset.level;
|
||||
const message = entry.querySelector('.log-message').textContent.toLowerCase();
|
||||
|
||||
const matchesFilter = currentFilter === 'all' || level === currentFilter;
|
||||
const matchesSearch = message.includes(searchTerm);
|
||||
|
||||
if (matchesFilter && matchesSearch) {
|
||||
entry.classList.remove('hidden');
|
||||
} else {
|
||||
entry.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="log-viewport">
|
||||
<?php if(empty($logEntries)): ?>
|
||||
<div class="empty-state">
|
||||
<h3>No Logs Found</h3>
|
||||
<p>Try adjusting your TPA filters or search term.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach($logEntries as $entry): ?>
|
||||
<?php $lvl = strtolower($entry['level']); ?>
|
||||
<div class="log-card level-<?= $lvl ?>">
|
||||
<div class="log-meta">
|
||||
<span class="log-level"><?= esc($entry['level']) ?></span>
|
||||
<span class="log-date"><?= esc($entry['date']) ?></span>
|
||||
</div>
|
||||
<div class="log-msg"><?= esc($entry['message']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-submit search after typing (optional)
|
||||
let typingTimer;
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
searchInput.addEventListener('keyup', () => {
|
||||
clearTimeout(typingTimer);
|
||||
typingTimer = setTimeout(() => {
|
||||
searchInput.closest('form').submit();
|
||||
}, 800);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
356
app/Views/sales/tracker_view.php
Normal file
356
app/Views/sales/tracker_view.php
Normal file
@ -0,0 +1,356 @@
|
||||
<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>
|
||||
Loading…
Reference in New Issue
Block a user