Merge branch 'dev' of bitbucket.org:jubilian/nhance into dev
This commit is contained in:
commit
9404a7a5bc
@ -17,6 +17,7 @@ class Acl
|
||||
'#^/getVerifiedPosUserData#' => ['public' => true],
|
||||
'#^/swagger#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||
'#^/getEmployeeActiveOrInactivePolicy#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/bulkEcardDownloadAsZip#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/sheet#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/sendextraparam#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/test/testingquerys#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
@ -28,6 +29,11 @@ class Acl
|
||||
'#^/util/view_log#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||
'#^/util/download_log#' => ['roles' => [ADMIN_ROLE_ID]],
|
||||
'#^/metaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/metaTpaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/sales/dashboard#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
'#^/sales#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
|
||||
|
||||
|
||||
// ===================== PUBLIC DOWNLOADS / FORMS =====================
|
||||
'#^/download-#' => ['public' => true],
|
||||
|
||||
@ -69,7 +69,7 @@ class Filters extends BaseConfig
|
||||
'before' => [
|
||||
'HttpRequestLog' => ['except' => 'cli/*'],
|
||||
'Cors',
|
||||
'AclFilter' => ['except' => ['login', 'logout', 'auth/*', 'oauth2callback','claim-form-download', 'claims-feedback-form', 'autobookstackLogin','employeeRest/*','processjob','getCommission']],
|
||||
'AclFilter' => ['except' => ['login', 'logout', 'auth/*', 'oauth2callback','claim-form-download', 'claims-feedback-form', 'autobookstackLogin','employeeRest/*','processjob','getCommission','downloadEmployeeEcardZip']],
|
||||
'SecurityInputFilter' => ['except' => ['/client/notification/create','/ticket/crud_mail_template/*','test_mail','leads/sendMail'] ],
|
||||
'GlobalPostFileUploadGuard'
|
||||
// 'csrf',
|
||||
|
||||
@ -33,6 +33,7 @@ $routes->get('/fedeploy', 'DeployController::fedeploy_view', ['filter' => 'authM
|
||||
$routes->post('/fedeploy', 'DeployController::fedeploy', ['filter' => 'authMVC']);
|
||||
$routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck');
|
||||
$routes->get('/metaDashboardDemo', 'TestingController::metaDashboardDemo');
|
||||
$routes->get('/metaTpaDashboardDemo', 'TestingController::metaTpaDashboardDemo');
|
||||
|
||||
// Reminder Mail Notification
|
||||
|
||||
@ -88,6 +89,7 @@ $routes->get('claim-form-download/(:any)', 'TicketController::downloadClaimForm/
|
||||
$routes->get('downloadClaimFile/(:any)', 'TicketController::downloadClaimFile/$1');
|
||||
$routes->match (['get','post'],"claims-feedback-form/(:any)/(:any)", "TicketController::viewClaimFeedbackForm/$1/$2");
|
||||
$routes->match (['get','post'],"claims-feedback-form/(:any)", "TicketController::viewClaimFeedbackForm/$1");
|
||||
$routes->get('downloadEmployeeEcardZip/(:any)', 'EmployeeController::downloadEmployeeEcardZip/$1');
|
||||
|
||||
|
||||
|
||||
@ -417,7 +419,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');
|
||||
@ -670,6 +672,7 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel
|
||||
$routes->post("hrFileUpload", "EmployeeRestController::hrFileUpload");
|
||||
$routes->post("updateHrFileUploadData", "EmployeeRestController::updateHrFileUploadData");
|
||||
$routes->post("getHrDashboad", "EmployeeRestController::getHrDashboad");
|
||||
$routes->post("getHrTpaDashboard", "EmployeeRestController::getHrTpaDashboard");
|
||||
|
||||
|
||||
//thz_master's
|
||||
@ -707,6 +710,7 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel
|
||||
$routes->get("downloadSampleExcel/(:any)", "EmployeeController::downloadSampleExcelFile/$1");
|
||||
});
|
||||
|
||||
$routes->post("bulkEcardDownloadAsZip", "EmployeeRestController::bulkEcardDownloadAsZip");
|
||||
$routes->get("hrFileDownload", "EmployeeRestController::hrFileDownload");
|
||||
$routes->post("hrFileList", "EmployeeRestController::hrFileList");
|
||||
$routes->get("getEmployeeActiveOrInactivePolicy", "EmployeeRestController::getEmployeeActiveOrInactivePolicy");
|
||||
@ -909,6 +913,10 @@ $routes->group('sales', function($routes) {
|
||||
|
||||
$routes->get('/', 'SalesController::index');
|
||||
|
||||
$routes->get('loadactivities', 'SalesController::loadactivities');
|
||||
|
||||
$routes->get('page/(:segment)', 'SalesController::noPage/$1');
|
||||
|
||||
// Get all leads with filters
|
||||
$routes->get('leads', 'SalesController::getLeads');
|
||||
|
||||
@ -989,6 +997,7 @@ $routes->group('sales', function($routes) {
|
||||
|
||||
|
||||
//Dashboard
|
||||
$routes->get('dashboard', 'SalesController::dashboard');
|
||||
$routes->get('branchLevelDashboard', 'SalesController::branchLevelDashboard');
|
||||
$routes->get('salesManagerLevelDashboard', 'SalesController::salesManagerLevelDashboard');
|
||||
});
|
||||
|
||||
@ -41,6 +41,7 @@ use App\Controllers\EmployeeServiceController;
|
||||
use App\Controllers\EmpDataServiceController;
|
||||
use App\Models\LeadsModel;
|
||||
use App\Models\ThzMasterModel;
|
||||
use App\Models\UserActivityHistoryModel;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
@ -78,6 +79,7 @@ class EmployeeController extends AdminController
|
||||
protected $userModel;
|
||||
protected $partnerEndorsementRequestModel;
|
||||
protected $LevelContactModel;
|
||||
protected $UserActivityHistoryModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -102,6 +104,7 @@ class EmployeeController extends AdminController
|
||||
$this->userModel = new userModel();
|
||||
$this->partnerEndorsementRequestModel = new PartnerEndorsementRequestModel();
|
||||
$this->LevelContactModel = new LevelContactModel();
|
||||
$this->UserActivityHistoryModel = new UserActivityHistoryModel();
|
||||
}
|
||||
|
||||
public function list()
|
||||
@ -4862,8 +4865,133 @@ class EmployeeController extends AdminController
|
||||
return $mail_response;
|
||||
}
|
||||
|
||||
public function downloadZip(){
|
||||
|
||||
|
||||
public function bulkEcardDownloadAsZipFromS3New($params)
|
||||
{
|
||||
try {
|
||||
|
||||
$limit = $params['limit'] ?? 100;
|
||||
$last_id = $params['last_id'] ?? 0;
|
||||
$zip_id = $params['zip_id'];
|
||||
|
||||
$employee_data = $this->employeePolicyModel->getEmployeeDataWithPolicyUsingClientPolicyIdOrEmployeePolicyIds($params, $limit, $last_id);
|
||||
|
||||
if (empty($employee_data)) {
|
||||
// No more records, move to Zipping phase
|
||||
return Jobs::addJob(['job_name' => 'getEmployeeEcardFromTmpFolderAndZipToS3', 'payload' => $params]);
|
||||
}
|
||||
|
||||
$tempPath = FCPATH . 'tmp/' . $params['folder_name'] . '/';
|
||||
if (!is_dir($tempPath)) mkdir($tempPath, 0777, true);
|
||||
|
||||
$s3 = \Config\Services::getS3Service();
|
||||
$current_last_id = $last_id;
|
||||
$pdf_count = 0;
|
||||
|
||||
foreach ($employee_data as $emp) {
|
||||
$current_last_id = $emp['emp_policy_id'];
|
||||
|
||||
|
||||
$s3_key = 'ecard_' . $emp['name'] . '(' . $emp['emp_code'] . ')' . '_' . $emp['tpa_id'] . '.pdf';
|
||||
$s3_key = $this->sanitizeFilePart($s3_key);
|
||||
|
||||
if ($s3->exists($s3_key)) {
|
||||
$s3_url = $s3->getPresignedUrl($s3_key);
|
||||
$pdf_content = file_get_contents($s3_url['url']);
|
||||
|
||||
if ($pdf_content !== false) {
|
||||
file_put_contents($tempPath . $s3_key, $pdf_content);
|
||||
$pdf_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need more batches
|
||||
$params['last_id'] = $current_last_id;
|
||||
$params['pdf_count'] = $pdf_count;
|
||||
$params['batch_no'] = ($params['batch_no'] ?? 1) + 1;
|
||||
|
||||
if (count($employee_data) == $limit) {
|
||||
return Jobs::addJob(['job_name' => 'bulkEcardDownloadAsZipFromS3', 'payload' => $params]);
|
||||
} else {
|
||||
return Jobs::addJob(['job_name' => 'getEmployeeEcardFromTmpFolderAndZipToS3', 'payload' => $params]);
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->logAndNotifyError($e, $params);
|
||||
}
|
||||
}
|
||||
|
||||
public function getEmployeeEcardFromTmpFolderAndZipToS3New($params)
|
||||
{
|
||||
try {
|
||||
$zipService = new \App\Libraries\ZipService();
|
||||
$source = FCPATH . 'tmp/' . $params['folder_name'];
|
||||
$zipName = $params['folder_name'] . '.zip';
|
||||
$s3_dest_key = 'exports/zips/' . $zipName;
|
||||
|
||||
// 1. Create Zip and Upload
|
||||
$result = $zipService->zipAndUploadS3($source, $s3_dest_key);
|
||||
|
||||
if ($result['status']) {
|
||||
// 2. IMPORTANT: Update the Activity Record with the S3 Key
|
||||
$history = $this->UserActivityHistoryModel->find($params['zip_id']);
|
||||
$misc = json_decode($history['misc_data'], true);
|
||||
$misc['status'] = 'completed';
|
||||
$misc['s3_key'] = $s3_dest_key;
|
||||
|
||||
$this->UserActivityHistoryModel->update($params['zip_id'], [
|
||||
'misc_data' => json_encode($misc)
|
||||
]);
|
||||
|
||||
$params['url'] = base_url('downloadEmployeeEcardZip/' . md5($params['zip_id']));
|
||||
}
|
||||
|
||||
// 3. Cleanup local files
|
||||
$this->recursiveRemoveDir($source);
|
||||
|
||||
// 4. Email HR
|
||||
return $this->sendMailToHrWithZipAttachments($params);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->logAndNotifyError($e, $params);
|
||||
}
|
||||
}
|
||||
|
||||
public function downloadEmployeeEcardZip($md5Id)
|
||||
{
|
||||
// Search by MD5 of ID
|
||||
$data = $this->UserActivityHistoryModel->where('MD5(CAST(id AS CHAR))', $md5Id)->first();
|
||||
|
||||
if (!$data) return "File link expired or invalid.";
|
||||
|
||||
$misc = json_decode($data['misc_data'], true);
|
||||
$s3Key = $misc['s3_key'] ?? null;
|
||||
|
||||
if (!$s3Key) return "File is still processing or failed to generate.";
|
||||
|
||||
$s3 = \Config\Services::getS3Service();
|
||||
$presigned = $s3->getPresignedUrl($s3Key, 15); // 15 mins expiry
|
||||
|
||||
return redirect()->to($presigned['url']);
|
||||
}
|
||||
|
||||
private function logAndNotifyError($e, $params)
|
||||
{
|
||||
$this->myLogger->logme('error', "[ECARD_ZIP]: " . $e->getMessage());
|
||||
$params['url'] = null; // Forces "Failed" email
|
||||
$this->sendMailToHrWithZipAttachments($params);
|
||||
}
|
||||
|
||||
private function recursiveRemoveDir($dir)
|
||||
{
|
||||
if (!is_dir($dir)) return;
|
||||
$files = array_diff(scandir($dir), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
(is_dir("$dir/$file")) ? $this->recursiveRemoveDir("$dir/$file") : unlink("$dir/$file");
|
||||
}
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -63,6 +63,7 @@ use App\Models\ClaimFilesModel;
|
||||
use App\Models\PolicyTransactionModel;
|
||||
use App\Models\TicketMailTemplateModel;
|
||||
use App\Models\TpaApiSeviceModel;
|
||||
use App\Models\UserActivityHistoryModel;
|
||||
use Composer\Pcre\Preg;
|
||||
use Kreait\Firebase\Factory;
|
||||
use Kreait\Firebase\Messaging\CloudMessage;
|
||||
@ -106,6 +107,7 @@ class EmployeeRestController extends AdminController
|
||||
protected $ticketMailTemplateModel;
|
||||
protected $employeeRetailPolicy;
|
||||
protected $batchFileModel;
|
||||
protected $UserActivityHistoryModel;
|
||||
|
||||
|
||||
public function __construct()
|
||||
@ -141,6 +143,7 @@ class EmployeeRestController extends AdminController
|
||||
$this->hrFileUploadModel = new HrFileUploadModel();
|
||||
$this->ticketMailTemplateModel = new TicketMailTemplateModel();
|
||||
$this->batchFileModel = new BatchFileModel();
|
||||
$this->UserActivityHistoryModel = new UserActivityHistoryModel();
|
||||
}
|
||||
|
||||
|
||||
@ -5498,9 +5501,15 @@ class EmployeeRestController extends AdminController
|
||||
// 🔐 Move this to .env in real projects
|
||||
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
|
||||
|
||||
$is_tpa_dashboard_enable = $this->clientPolicyModel
|
||||
->select('tpa.dashboard_id')
|
||||
->join('tpa', 'client_policy.tpa_id = tpa.id')
|
||||
->where('client_policy.id', $received_data['client_policy_id'])
|
||||
->where('tpa.dashboard_id IS NOT NULL')
|
||||
->first();
|
||||
|
||||
$payload = [
|
||||
'resource' => [
|
||||
// 'dashboard' => 1
|
||||
'dashboard' => 2
|
||||
],
|
||||
'params' => (object)['client_policy' => $received_data['client_policy_id']], // MUST be object for Metabase
|
||||
@ -5520,8 +5529,10 @@ class EmployeeRestController extends AdminController
|
||||
'status' => 'success',
|
||||
'message' => 'Form data received successfully!',
|
||||
'data' => [
|
||||
'metabaseToken' => $token,
|
||||
'metabaseUrl' => 'https://nsights.nhanceindia.in']
|
||||
'metabaseToken' => $token,
|
||||
'metabaseUrl' => 'https://nsights.nhanceindia.in'
|
||||
],
|
||||
'is_tpa_dashboard_enable' => !empty($is_tpa_dashboard_enable ?? []),
|
||||
]);
|
||||
// }
|
||||
|
||||
@ -5530,6 +5541,73 @@ class EmployeeRestController extends AdminController
|
||||
'metabaseUrl' => 'https://nsights.nhanceindia.in',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getHrTpaDashboard($client_policy_id = null, $return_type = 'api')
|
||||
{
|
||||
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
|
||||
|
||||
if ($return_type === 'api') {
|
||||
|
||||
$received_data = $this->request->getJSON(true) ?? [];
|
||||
$policy_id = $received_data['client_policy_id'] ?? null;
|
||||
|
||||
} else {
|
||||
// Internal call
|
||||
$policy_id = $client_policy_id;
|
||||
}
|
||||
|
||||
if (empty($policy_id)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'message' => 'Client Policy ID is required.',
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
|
||||
$client_policy_data = $this->clientPolicyModel
|
||||
->select('tpa.dashboard_id')
|
||||
->join('tpa', 'client_policy.tpa_id = tpa.id')
|
||||
->where('client_policy.id', $policy_id)
|
||||
->first();
|
||||
|
||||
$database_id = isset($client_policy_data['dashboard_id']) ? (int) $client_policy_data['dashboard_id'] : null;
|
||||
|
||||
if (empty($database_id)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'message' => 'There is no dashboard for this TPA.',
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'resource' => [
|
||||
'dashboard' => $database_id
|
||||
],
|
||||
'exp' => time() + (10 * 60),
|
||||
'params' => (object)[]
|
||||
];
|
||||
|
||||
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
|
||||
|
||||
if ($return_type === 'internal') {
|
||||
|
||||
// Return raw data for internal usage
|
||||
return [
|
||||
'metabaseToken' => $token,
|
||||
'metabaseUrl' => 'https://nsights.nhanceindia.in'
|
||||
];
|
||||
}
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'message' => 'Dashboard fetched successfully.',
|
||||
'data' => [
|
||||
'metabaseToken' => $token,
|
||||
'metabaseUrl' => 'https://nsights.nhanceindia.in'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// get policy files
|
||||
@ -5648,5 +5726,55 @@ class EmployeeRestController extends AdminController
|
||||
|
||||
}
|
||||
|
||||
public function bulkEcardDownloadAsZipNew()
|
||||
{
|
||||
$received_data = $this->request->getJSON(true) ?? [];
|
||||
|
||||
if (empty($received_data['client_policy_id']) && empty($received_data['emp_policy_ids'])) {
|
||||
return $this->respond(['status' => false, 'message' => 'client_policy_id or employee_policy_ids is required'], 400);
|
||||
}
|
||||
|
||||
if (empty($received_data['hr_id'])) {
|
||||
return $this->respond(['status' => false, 'message' => 'hr_id is required'], 400);
|
||||
}
|
||||
|
||||
$employee_data = $this->employeePolicyModel->getEmployeeDataWithPolicyUsingClientPolicyIdOrEmployeePolicyIds($received_data, 1);
|
||||
|
||||
if (empty($employee_data)) {
|
||||
return $this->respond(['status' => false, 'message' => 'No employee data found'], 404);
|
||||
}
|
||||
|
||||
// Define naming convention
|
||||
$folderName = 'bulk_ecards_' . ($employee_data[0]['client_short_name'] ?? 'export') . '_' . date('Ymd_His');
|
||||
|
||||
$insert_data = [
|
||||
'user_id' => $received_data['hr_id'],
|
||||
'user_type' => 'hr',
|
||||
'activity' => 'download_employees_ecard_zip',
|
||||
'misc_data' => json_encode([
|
||||
'folder_name' => $folderName,
|
||||
'status' => 'pending',
|
||||
's3_key' => null
|
||||
])
|
||||
];
|
||||
|
||||
$zip_id = $this->UserActivityHistoryModel->insert($insert_data);
|
||||
|
||||
$payload = array_merge($received_data, [
|
||||
'zip_id' => $zip_id,
|
||||
'folder_name' => $folderName,
|
||||
'batch_no' => 1,
|
||||
'last_id' => 0,
|
||||
'limit' => 100
|
||||
]);
|
||||
|
||||
// Dispatch job
|
||||
Jobs::addJob(['job_name' => 'bulkEcardDownloadAsZipFromS3', 'payload' => $payload]);
|
||||
|
||||
return $this->respond([
|
||||
'status' => true,
|
||||
'message' => 'Process started. You will receive an email with the download link shortly.'
|
||||
], 200);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -7,6 +7,7 @@ use App\Models\SalesActualLeadModel;
|
||||
use App\Models\SalesContactPersonModel;
|
||||
use App\Models\SalesActivityModel;
|
||||
use App\Models\SalesLeadNoteModel;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
@ -18,6 +19,7 @@ class SalesController extends BaseController
|
||||
protected $contactModel;
|
||||
protected $activityModel;
|
||||
protected $noteModel;
|
||||
protected $userModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -25,17 +27,104 @@ class SalesController extends BaseController
|
||||
$this->contactModel = new SalesContactPersonModel();
|
||||
$this->activityModel = new SalesActivityModel();
|
||||
$this->noteModel = new SalesLeadNoteModel();
|
||||
$this->userModel = new UserModel();
|
||||
}
|
||||
|
||||
|
||||
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 loadactivities(){
|
||||
$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();
|
||||
$role = get_role_id();
|
||||
$team_id = user_team();
|
||||
|
||||
$data = [
|
||||
'users' => [],
|
||||
'sales_manager_ids' => []
|
||||
];
|
||||
|
||||
$row = $db->table('user_profiles')->select('*')
|
||||
->where('is_active', 1)->where('id', $logged_user_id)
|
||||
->get()->getRow();
|
||||
|
||||
$nhance_branch_id = $row ? $row->nhance_branch_id : null;
|
||||
|
||||
// Is the logged-in user a Sales Manager? (Role 4, Team 5)
|
||||
if ($role == 4 && in_array(5, $team_id)) {
|
||||
$data['sales_manager_ids'] = [$logged_user_id];
|
||||
|
||||
$data['users'] = [
|
||||
[
|
||||
'id' => $row->id,
|
||||
'first_name' => $row->first_name,
|
||||
'nhance_branch_id' => $nhance_branch_id
|
||||
]
|
||||
];
|
||||
|
||||
}
|
||||
// Otherwise fetch ALL sales managers in this branch
|
||||
elseif (in_array($role,[1,2,3,4,5])) {
|
||||
|
||||
$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 '<div class="col-12">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h3>Oops! ' . esc($page_type) . ' is under construction.</h3>
|
||||
<img src="https://omjsblog.files.wordpress.com/2023/07/errorimg.png" alt="Error Image" style="max-width: 100%; height: auto;">
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
echo view('layout/footer', $data);
|
||||
}
|
||||
|
||||
public function completeActivity($id) {
|
||||
@ -50,9 +139,11 @@ class SalesController extends BaseController
|
||||
]);
|
||||
|
||||
// 2. Handle follow-up if requested
|
||||
$google_calender_response = [];
|
||||
if (!empty($data['schedule_followup']) && $data['schedule_followup'] === 'yes') {
|
||||
|
||||
$activity = $this->activityModel->find($id);
|
||||
$this->activityModel->insert([
|
||||
$activity_data = [
|
||||
'lead_id' => $activity['lead_id'],
|
||||
'activity_type' => $data['followup_type'],
|
||||
'notes' => $data['followup_notes'],
|
||||
@ -60,10 +151,14 @@ class SalesController extends BaseController
|
||||
'assigned_to' => $activity['assigned_to'],
|
||||
'status' => 'pending',
|
||||
'created_by' => $this->getUserId()
|
||||
]);
|
||||
];
|
||||
$this->activityModel->insert($activity_data);
|
||||
|
||||
// add google calender event
|
||||
$google_calender_response = $this->addCalenderEvent($activity_data);
|
||||
}
|
||||
|
||||
return $this->respond(['status' => 'success', 'message' => 'Activity updated']);
|
||||
return $this->respond(['status' => 'success', 'message' => 'Activity updated', 'google_calender_response' => $google_calender_response]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
@ -371,7 +466,8 @@ class SalesController extends BaseController
|
||||
'total' => $result['total'],
|
||||
'limit' => $limit,
|
||||
'offset' => $offset
|
||||
]);
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
@ -450,6 +546,9 @@ class SalesController extends BaseController
|
||||
if (!$this->activityModel->insert($data)) {
|
||||
return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
// add google calender event
|
||||
$response = $this->addCalenderEvent($data);
|
||||
|
||||
$activityId = $this->activityModel->getInsertID();
|
||||
$activity = $this->activityModel->find((int)$activityId);
|
||||
@ -457,7 +556,8 @@ class SalesController extends BaseController
|
||||
return $this->respondCreated([
|
||||
'status' => 'success',
|
||||
'message' => 'Activity created successfully',
|
||||
'data' => $activity
|
||||
'data' => $activity,
|
||||
'google_calender_response' => $response
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
@ -672,12 +772,60 @@ class SalesController extends BaseController
|
||||
}
|
||||
|
||||
|
||||
// ==================== Dashboard ====================
|
||||
// ==================== Dashboard ====================
|
||||
public function dashboard(){
|
||||
|
||||
public function branchLevelDashboard()
|
||||
$logged_user_id = get_session_userid();
|
||||
$role = get_role_id();
|
||||
$team_id = user_team();
|
||||
$payload = $this->request->getGet();
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
$row = $db->table('user_profiles')
|
||||
->select('*')
|
||||
->where('is_active', 1)
|
||||
->where('id', $logged_user_id)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$nhance_branch_id = $row ? $row['nhance_branch_id'] : null;
|
||||
|
||||
// dd($logged_user_id, $nhance_branch_id, $role, $team_id );
|
||||
|
||||
if (in_array($role,[5])) {
|
||||
|
||||
$sales_manager_ids = array_column(
|
||||
$db->table('user_profiles up')
|
||||
->select('up.id')
|
||||
->join('user_teams ut', 'ut.user_id = up.id')
|
||||
->where([
|
||||
'up.is_active' => 1,
|
||||
'ut.is_active' => 1,
|
||||
'up.role' => 4,
|
||||
'ut.team_id' => 5,
|
||||
'up.nhance_branch_id' => $nhance_branch_id
|
||||
])
|
||||
->get()
|
||||
->getResultArray(),
|
||||
'id'
|
||||
);
|
||||
$this->branchLevelDashboard($nhance_branch_id,$sales_manager_ids);
|
||||
|
||||
}
|
||||
elseif ($role == 4 && in_array(5, $team_id)) {
|
||||
$sales_manager_ids = [$logged_user_id];
|
||||
$this->salesManagerLevelDashboard($logged_user_id,$sales_manager_ids, $payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function branchLevelDashboard($branchId,$sales_manager_ids)
|
||||
{
|
||||
$data['tab_name'] = 'Dashboard';
|
||||
$data['page_name'] = 'Dashboard';
|
||||
// Hardcoded branch ID as requested
|
||||
$branchId = 1;
|
||||
// $branchId = 1;
|
||||
|
||||
try {
|
||||
// 1. Lead Statistics
|
||||
@ -692,11 +840,24 @@ class SalesController extends BaseController
|
||||
|
||||
// 3. Team Performance (Aggregating activity counts per user)
|
||||
$db = \Config\Database::connect();
|
||||
// Final safe check
|
||||
if (empty($sales_manager_ids)) {
|
||||
// No valid IDs — skip queries or return empty
|
||||
$teamPerformance = [];
|
||||
$recentActivities = [];
|
||||
$leadsOverview = [];
|
||||
} else {
|
||||
|
||||
if (empty($sales_manager_ids) || !is_array($sales_manager_ids)) {
|
||||
$sales_manager_ids = array_filter((array) $sales_manager_ids); // removes null, "", 0
|
||||
}
|
||||
|
||||
$teamPerformance = $db->table('user_profiles as u')
|
||||
->select('u.first_name, u.last_name, u.profile as role,
|
||||
(SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id) as total_acts,
|
||||
(SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id AND status = "completed") as done_acts')
|
||||
->where('u.nhance_branch_id', $branchId)
|
||||
->whereIn('u.id', $sales_manager_ids)
|
||||
->where('u.is_active', 1)
|
||||
->get()->getResultArray();
|
||||
|
||||
@ -706,11 +867,48 @@ class SalesController extends BaseController
|
||||
->orderBy('sales_activities.scheduled_date', 'DESC')
|
||||
->limit(6)
|
||||
->findAll();
|
||||
|
||||
$sales_manager_ids = array_values(array_map('intval', $sales_manager_ids));
|
||||
$recentActivities = $db->table('sales_activities sa')
|
||||
->select('sa.activity_id,sa.lead_id,sa.activity_type,sa.scheduled_date,sa.status,sa.assigned_to,sal.company_name,up.first_name AS assigned_to_name')
|
||||
->join('(SELECT lead_id, MAX(company_name) AS company_name FROM sales_actual_leads GROUP BY lead_id) sal','sal.lead_id = sa.lead_id','left')
|
||||
->join('user_profiles up', 'up.id = sa.assigned_to', 'left')
|
||||
->orderBy('sa.scheduled_date', 'DESC')
|
||||
->limit(6)
|
||||
->whereIn('sa.assigned_to', $sales_manager_ids)
|
||||
->get()->getResultArray();
|
||||
|
||||
// 5. All Leads Overview
|
||||
$leadsOverview = $db->table('sales_actual_leads sal')
|
||||
->select('sal.lead_id,sal.company_name,sal.status,up.first_name AS assigned_to,
|
||||
COUNT(DISTINCT sa.activity_id) AS activities,
|
||||
COUNT(DISTINCT l.id) AS opportunities
|
||||
')
|
||||
->join('user_profiles up', 'up.id = sal.assigned_to', 'left')
|
||||
->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left')
|
||||
->join('leads l', 'l.actual_lead_id = sal.lead_id', 'left')
|
||||
->groupBy('sal.lead_id, sal.company_name, sal.status, up.first_name')
|
||||
->having('COUNT(DISTINCT sa.activity_id) + COUNT(DISTINCT l.id) >', 0) // ← this line
|
||||
->orderBy('sal.created_at', 'DESC')
|
||||
->whereIn('sal.assigned_to', $sales_manager_ids)
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
// 6. activityBreakdown
|
||||
$total = $db->table('sales_activities')->countAll();
|
||||
|
||||
$activityBreakdown = $db->table('sales_activities')
|
||||
->select("activity_type, COUNT(*) AS total, ROUND(COUNT(*) * 100.0 / {$total}, 0) AS percentage", false)
|
||||
->groupBy('activity_type')
|
||||
->orderBy('total', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$teamPerformance = array_filter($teamPerformance, function($row) {
|
||||
return ($row['total_acts'] + $row['done_acts']) > 0;
|
||||
});
|
||||
|
||||
// 5. All Leads Overview
|
||||
$leadsOverview = $this->leadModel->select('sales_actual_leads.*, user_profiles.first_name, user_profiles.last_name')
|
||||
->join('user_profiles', 'user_profiles.id = sales_actual_leads.assigned_to', 'left')
|
||||
->findAll();
|
||||
|
||||
$data = [
|
||||
'total_leads' => $stats['total'],
|
||||
@ -719,7 +917,10 @@ class SalesController extends BaseController
|
||||
'pipeline_value' => '15.0L', // Hardcoded placeholder from PDF [cite: 14]
|
||||
'team' => $teamPerformance,
|
||||
'recent_acts' => $recentActivities,
|
||||
'leads_overview' => $leadsOverview
|
||||
'leads_overview' => $leadsOverview,
|
||||
'activity_breakdown'=> $activityBreakdown,
|
||||
'tab_name' => "Sales Dashboard",
|
||||
'page_name' => "Sales Dashboard"
|
||||
];
|
||||
|
||||
// dd($data);
|
||||
@ -733,22 +934,45 @@ class SalesController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function salesManagerLevelDashboard()
|
||||
public function salesManagerLevelDashboard($userId,$sales_manager_ids, $payload = [])
|
||||
{
|
||||
$userId = 1;
|
||||
// $userId = get_session_userid();
|
||||
// $userId = 1;
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
try {
|
||||
|
||||
// $payload = $this->request->getGet();
|
||||
$current_fin_year = $payload['fy'] ?? getCurrentFinancialYear();
|
||||
|
||||
|
||||
$fin_years = $db->table('sales_target')
|
||||
->select('fy_year')
|
||||
->where('user_id', $userId)
|
||||
->orderBy('fy_year', 'desc')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$fin_years = array_column($fin_years, 'fy_year');
|
||||
|
||||
if(empty($fin_years)){
|
||||
$fin_years[] = $current_fin_year;
|
||||
}
|
||||
|
||||
$target = $db->table('sales_target')
|
||||
->where('user_id', $userId)
|
||||
->where('fy_year', '2025-2026')
|
||||
->get()->getRowArray();
|
||||
->where('fy_year', $current_fin_year)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$targetAmount = $target['target_amount'] ?? 0.00;
|
||||
|
||||
// get achieved amount from leads table
|
||||
$achievedAmount = $this->getUserAchievedAmount($current_fin_year, $userId);
|
||||
|
||||
$targetAmount = $target['target_amount'] ?? 2500000.00;
|
||||
$achievedAmount = 600000.00;
|
||||
$remainingAmount = $targetAmount - $achievedAmount;
|
||||
$achievementPercent = ($targetAmount > 0) ? round(($achievedAmount / $targetAmount) * 100) : 0;
|
||||
// $achievementPercent = ($targetAmount > 0) ? round(($achievedAmount / $targetAmount) * 100) : 0;
|
||||
$achievementPercent = ($targetAmount > 0) ? min(100, round(($achievedAmount / $targetAmount) * 100)) : 0;
|
||||
|
||||
$activitySummary = [
|
||||
'total' => $this->activityModel->where('assigned_to', $userId)->countAllResults(),
|
||||
@ -779,9 +1003,15 @@ class SalesController extends BaseController
|
||||
'lead_count' => $myLeadsCount,
|
||||
'upcoming' => $upcomingActivities,
|
||||
'recent_leads' => $recentLeads,
|
||||
'user_name' => session()->get('first_name') ?? 'John Doe'
|
||||
'fin_years' => $fin_years,
|
||||
'display_fin_years' => format_financial_year($current_fin_year),
|
||||
'user_name' => get_session_userdata()->first_namee ?? '',
|
||||
'tab_name' => "Sales Dashboard",
|
||||
'page_name' => "Sales Dashboard"
|
||||
];
|
||||
|
||||
// dd($data);
|
||||
|
||||
// return view('sales/my_dashboard_view', $data);
|
||||
$this->loadLayout('sales/sales_manager_level_dashboard', $data);
|
||||
|
||||
@ -789,4 +1019,107 @@ class SalesController extends BaseController
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function getUserAchievedAmount($financialYear, $userId)
|
||||
{
|
||||
// Split the string into two years
|
||||
$years = explode('-', $financialYear);
|
||||
$startYear = $years[0]; // 2025
|
||||
$endYear = $years[1]; // 2026
|
||||
|
||||
// Create the timestamps
|
||||
$startFY = $startYear . '-04-01 00:00:00';
|
||||
$endFY = $endYear . '-03-31 23:59:59';
|
||||
|
||||
$achievedAmountData = $this->leadModel
|
||||
->select('SUM(leads.premium_amount) as achieved_amount')
|
||||
->join('leads', 'sales_actual_leads.lead_id = leads.actual_lead_id')
|
||||
->where('sales_actual_leads.assigned_to', $userId)
|
||||
->where('leads.status', 'won')
|
||||
->where('leads.updated_at >=', $startFY)
|
||||
->where('leads.updated_at <=', $endFY)
|
||||
->findAll();
|
||||
|
||||
return $achievedAmountData[0]['achieved_amount'] ?? 0.00;
|
||||
}
|
||||
|
||||
public function addCalenderEvent($input)
|
||||
{
|
||||
try {
|
||||
|
||||
log_message('error', '[GOOGLE_CALENDER] Calendar Event Input: ' . json_encode($input));
|
||||
|
||||
/* ---------------- VALIDATION ---------------- */
|
||||
|
||||
if (empty($input['lead_id']) || empty($input['assigned_to']) || empty($input['scheduled_date'])) {
|
||||
log_message('error', '[GOOGLE_CALENDER] Calendar Event Missing Required Data');
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Required data missing'
|
||||
];
|
||||
}
|
||||
|
||||
/* ---------------- FETCH DATA ---------------- */
|
||||
|
||||
$lead_data = $this->leadModel
|
||||
->where('lead_id', $input['lead_id'])
|
||||
->first();
|
||||
|
||||
if (!$lead_data) {
|
||||
log_message('error', '[GOOGLE_CALENDER] Lead Not Found: ' . $input['lead_id']);
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Lead not found'
|
||||
];
|
||||
}
|
||||
|
||||
$user_data = $this->userModel
|
||||
->where('id', $input['assigned_to'])
|
||||
->first();
|
||||
|
||||
if (!$user_data) {
|
||||
log_message('error', '[GOOGLE_CALENDER] User Not Found: ' . $input['assigned_to']);
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Assigned user not found'
|
||||
];
|
||||
}
|
||||
|
||||
/* ---------------- SUMMARY ---------------- */
|
||||
|
||||
$summary = ucfirst($input['activity_type']) .
|
||||
' with ' .
|
||||
$lead_data['company_name'];
|
||||
|
||||
/* ---------------- GOOGLE PAYLOAD ---------------- */
|
||||
|
||||
$eventData = [
|
||||
'summary' => $summary,
|
||||
'meeting_date' => $input['scheduled_date'],
|
||||
'description' => $input['notes'] ?? '',
|
||||
'emails' => [$user_data['email']],
|
||||
];
|
||||
|
||||
log_message('error', '[GOOGLE_CALENDER] Google Calendar Payload: ' . json_encode($eventData));
|
||||
|
||||
/* ---------------- API CALL ---------------- */
|
||||
|
||||
$response = add_google_calender_event($eventData);
|
||||
|
||||
log_message('error', '[GOOGLE_CALENDER] Google Calendar Response: ' . json_encode($response));
|
||||
|
||||
return $response;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
log_message('error', '[GOOGLE_CALENDER] Calendar Event Exception: ' . $e->getMessage());
|
||||
log_message('error', '[GOOGLE_CALENDER] Calendar Event Trace: ' . $e->getTraceAsString());
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Calendar event failed',
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ namespace App\Controllers;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\EmployeePolicyModel;
|
||||
use App\Models\ClientPolicyModel;
|
||||
use App\Models\InsurerBranchModel;
|
||||
use App\Models\RFQModel;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
@ -23,10 +24,12 @@ class TestingController extends BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
protected $myLogger;
|
||||
protected $ClientPolicyModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->myLogger = \Config\Services::mylogger();
|
||||
$this->ClientPolicyModel = new ClientPolicyModel();
|
||||
}
|
||||
|
||||
public function saveForm()
|
||||
@ -1060,4 +1063,59 @@ class TestingController extends BaseController
|
||||
return $this->respond(['status' => 'failed', 'code' => '500' , 'message' => 'Error: ' . $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
public function metaTpaDashboardDemo()
|
||||
{
|
||||
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
|
||||
$policy_id = $this->request->getGet('client_policy') ?? 4687;
|
||||
|
||||
$client_policy_data = $this->ClientPolicyModel
|
||||
->select('tpa.dashboard_id')
|
||||
->join('tpa', 'client_policy.tpa_id = tpa.id')
|
||||
->where('client_policy.id', $policy_id)
|
||||
->first();
|
||||
|
||||
$database_id = isset($client_policy_data['dashboard_id']) ? (int) $client_policy_data['dashboard_id'] : null;
|
||||
|
||||
if (empty($database_id)) {
|
||||
|
||||
if ($this->request->getGet('api') == 1) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'message' => 'There is no dashboard for this TPA.',
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
|
||||
return view('errors/404', [
|
||||
'message' => 'There is no dashboard for this TPA.'
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'resource' => [
|
||||
'dashboard' => $database_id
|
||||
],
|
||||
'exp' => time() + (10 * 60), // 10 minutes
|
||||
'params' => (object)[]
|
||||
];
|
||||
|
||||
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
|
||||
|
||||
if ($this->request->getGet('api') == 1) {
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'message' => 'Form data received successfully!',
|
||||
'data' => [
|
||||
'metabaseToken' => $token,
|
||||
'metabaseUrl' => 'https://nsights.nhanceindia.in'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
return view('meta_dashboard_demo_one', [
|
||||
'metabaseToken' => $token,
|
||||
'metabaseUrl' => 'https://nsights.nhanceindia.in',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1210,7 +1210,7 @@ if (!function_exists('map_relationship')) {
|
||||
* Extract identity from POST body or GET params.
|
||||
* Looks for 'email' or 'mobile_number'.
|
||||
*/
|
||||
function resolveIdentity($request): ?string
|
||||
function resolveIdentity($request): ?string
|
||||
{
|
||||
// Try POST body first
|
||||
$email = $request->getPost('email');
|
||||
@ -1251,7 +1251,7 @@ if (!function_exists('map_relationship')) {
|
||||
}
|
||||
|
||||
|
||||
function recordRateLimitFailure(string $context = 'authApi'): void
|
||||
function recordRateLimitFailure(string $context = 'authApi'): void
|
||||
{
|
||||
/** @var IncomingRequest $request */
|
||||
$request = \Config\Services::request();
|
||||
@ -1276,4 +1276,64 @@ if (!function_exists('map_relationship')) {
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('getCurrentFinancialYear')) {
|
||||
function getCurrentFinancialYear()
|
||||
{
|
||||
// Get current year and month
|
||||
$date = new DateTime();
|
||||
$currentYear = (int)$date->format('Y');
|
||||
$currentMonth = (int)$date->format('m');
|
||||
|
||||
// If month is Jan, Feb, or March, we are still in the previous year's FY
|
||||
if ($currentMonth < 4) {
|
||||
$startYear = $currentYear - 1;
|
||||
$endYear = $currentYear;
|
||||
} else {
|
||||
$startYear = $currentYear;
|
||||
$endYear = $currentYear + 1;
|
||||
}
|
||||
|
||||
return $startYear . '-' . $endYear;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('format_financial_year')) {
|
||||
function format_financial_year(string $financialYear): string
|
||||
{
|
||||
if (empty($financialYear) || !str_contains($financialYear, '-')) {
|
||||
return $financialYear;
|
||||
}
|
||||
|
||||
$years = explode('-', $financialYear);
|
||||
|
||||
// Ensure we have both parts
|
||||
$startYear = $years[0] ?? '';
|
||||
$endYear = $years[1] ?? '';
|
||||
|
||||
return "APR " . $startYear . " - MAR " . $endYear;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!function_exists('add_google_calender_event')){
|
||||
function add_google_calender_event($data){
|
||||
|
||||
$calendar = new \App\Libraries\GoogleCalendarService();
|
||||
|
||||
// Check if user is authenticated without passing tokens manually
|
||||
if (!$calendar->isReady()) {
|
||||
return ['status' => 'failed', 'code' => '404' , 'message' => 'Google Access Token Expired'];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $calendar->createEvent($data);
|
||||
return ['status' => 'success', 'code' => '200' , 'message' => 'Follow-up Saved', 'response' => $response];
|
||||
} catch (\Exception $e) {
|
||||
return ['status' => 'failed', 'code' => '500' , 'message' => 'Error: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -2260,9 +2260,12 @@ class EmployeePolicyModel extends Model
|
||||
employees.id as emp_id,
|
||||
tpa.short_name,
|
||||
employee_polices.tpa_id,
|
||||
client_policy.policy_no
|
||||
client_policy.policy_no,
|
||||
clients.client_name as client_name,
|
||||
clients.short_name as client_short_name
|
||||
')
|
||||
->join('client_policy', 'client_policy.id = employee_polices.client_policy_id')
|
||||
->join('clients', 'clients.id = client_policy.client_id')
|
||||
->join('employees', 'employees.id = employee_polices.employee_id')
|
||||
->join('tpa', 'tpa.id = client_policy.tpa_id')
|
||||
->where([
|
||||
|
||||
@ -10,6 +10,7 @@ class LeadsModel extends Model
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'actual_lead_id',
|
||||
'lead_type',
|
||||
'issuer',
|
||||
'client_type',
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -19,5 +19,6 @@ class TPAModel extends Model
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"is_active",
|
||||
"dashboard_id",
|
||||
];
|
||||
}
|
||||
|
||||
@ -2077,6 +2077,37 @@
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<?php if (in_array(get_role_id(), [1, 2, 3, 4]) || in_array(SALES_TEAM_ID, user_team())) { ?>
|
||||
<li class="li-seperate" id="app-sales-tracker-li">
|
||||
<a id="app_sales_tracker" href="#sidebarSalesTrackermenu" data-toggle="collapse" class="waves-effect img-inactive" style="color: grey;">
|
||||
<img
|
||||
src="<?= base_url() . "public"; ?>/assets/images/sales_tracker_dark_sb.png" alt="Logo" height="20">
|
||||
<span style="color: #02a8b5;">Sales Tracker</span>
|
||||
</a>
|
||||
<div class="collapse" id="sidebarSalesTrackermenu">
|
||||
<ul class="nav-second-level">
|
||||
<li>
|
||||
<a href="<?= base_url('/sales/dashboard') ?>"> <i class="ri-dashboard-line"></i> Dashboard </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= base_url('/sales') ?>"> <i class="ri-group-line"></i> Leads </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= base_url('/sales/loadactivities') ?>"><i class="ri-flashlight-line"></i> Activities </a>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<a href="<?= base_url('/sales/page/Opportunities') ?>"> <i class="ri-checkbox-circle-line"></i> Opportunities </a>
|
||||
</li> -->
|
||||
<!-- <li>
|
||||
<a href="<?= base_url('sales/page/Reports') ?>"><i class="ri-bar-chart-line"></i> Reports </a>
|
||||
</li> -->
|
||||
<!-- <li>
|
||||
<a href="<?= base_url('sales/page/Team') ?>"><i class="ri-bar-chart-line"></i> Sales Team </a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<!-- Tickets -->
|
||||
<?php if (in_array(get_role_id(), [1, 2, 3, 4, 5]) || in_array(CLAIMS_TEAM_ID, user_team())) { ?>
|
||||
<li class="li-seperate" id="tickets-li">
|
||||
|
||||
@ -131,6 +131,8 @@
|
||||
enctype="multipart/form-data">
|
||||
|
||||
<input type="hidden" name="id" id="leads_primarykey">
|
||||
<input type="hidden" name="actual_lead_id" id="actual_lead_id" value="<?= isset($actual_lead_id) ? $actual_lead_id : 0 ?>">
|
||||
|
||||
|
||||
<div class="card" id="collapseOne">
|
||||
<div class="card-body">
|
||||
@ -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);
|
||||
|
||||
@ -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 || '');
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@
|
||||
|
||||
<input type="hidden" name="id" id="leads_primarykey">
|
||||
<input type="hidden" name="lead_form_type" id="lead_form_type_id" value="2">
|
||||
<input type="hidden" name="actual_lead_id" id="actual_lead_id" value="<?= isset($actual_lead_id) ? $actual_lead_id : 0 ?>">
|
||||
|
||||
<div class="card" id="collapseOne">
|
||||
<div class="card-body">
|
||||
|
||||
@ -621,7 +621,7 @@
|
||||
console.log('Business Team Logged in.');
|
||||
}
|
||||
|
||||
getURLParamsForReport()
|
||||
// getURLParamsForReport()
|
||||
})
|
||||
|
||||
$(document).ready(function(){
|
||||
@ -1086,10 +1086,10 @@
|
||||
|
||||
if(res.data.client_type == 2){
|
||||
let data1 = policyListByClient[res.data.client_id];
|
||||
appendPolicies(data1, res.pt_id);
|
||||
appendPolicies(data1, res.data.client_policy_id);
|
||||
}else{
|
||||
let data2 = policy_list[res.data.client_branch_id];
|
||||
appendPolicies(data2, res.pt_id);
|
||||
appendPolicies(data2, res.data.client_policy_id);
|
||||
}
|
||||
|
||||
setTimeout(function(){
|
||||
@ -2957,9 +2957,9 @@
|
||||
|
||||
if (pt_id != 0) {
|
||||
hide_list_show_add();
|
||||
setTimeout(() =>
|
||||
getPolicyTransactionDataForEndorsementEdit(pt_id),
|
||||
1500); // Reduced timeout
|
||||
// setTimeout(() =>
|
||||
getPolicyTransactionDataForEndorsementEdit(pt_id)
|
||||
// 1500); // Reduced timeout
|
||||
} else {
|
||||
show_list_hide_add();
|
||||
}
|
||||
|
||||
@ -460,7 +460,11 @@ table.dataTable thead th {
|
||||
// select2 document ready
|
||||
$(document).ready(function() {
|
||||
|
||||
getClientAndBranchAndPolicy()
|
||||
// getClientAndBranchAndPolicy()
|
||||
getClientAndBranchAndPolicy(function() {
|
||||
getURLParamsForReport();
|
||||
});
|
||||
|
||||
addHTMLInput();
|
||||
|
||||
<?php if (session()->has('create_failed')) : ?>
|
||||
@ -632,7 +636,7 @@ table.dataTable thead th {
|
||||
|
||||
}
|
||||
|
||||
function getClientAndBranchAndPolicy() {
|
||||
function getClientAndBranchAndPolicy(callback) {
|
||||
$.ajax({
|
||||
url: '<?= base_url("/util/getClientAndBranchAndPolicy") ?>',
|
||||
type: "GET",
|
||||
@ -652,6 +656,11 @@ table.dataTable thead th {
|
||||
console.log('No data found');
|
||||
}
|
||||
|
||||
// ✅ Data ellam set aanadhuku aparam callback call panrom
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error(xhr.responseText);
|
||||
|
||||
1300
app/Views/sales/activity_view.php
Normal file
1300
app/Views/sales/activity_view.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,38 @@
|
||||
.stat-change { font-size: 11px; margin-top: 8px; font-weight: 600; }
|
||||
.text-success { color: #48bb78; }
|
||||
|
||||
.card-hero {
|
||||
background: white;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #edf2f7;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* The Gradient Overlay Effect */
|
||||
.card-hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px; /* Thin line at the top */
|
||||
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
.card-hero:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Optional: Subtle Background Gradient */
|
||||
.card-hero.gradient-bg {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
|
||||
}
|
||||
|
||||
.main-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 30px; }
|
||||
.table-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
|
||||
@ -29,10 +61,18 @@
|
||||
.status-pill { padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 700; display: inline-block; }
|
||||
.status-completed { background: #f0fff4; color: #38a169; }
|
||||
.status-pending { background: #fffaf0; color: #dd6b20; }
|
||||
.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; }
|
||||
|
||||
.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; }
|
||||
|
||||
</style>
|
||||
|
||||
<div class="dash-container">
|
||||
<div class="top-header">
|
||||
<!-- <div class="top-header">
|
||||
<div>
|
||||
<h2 style="font-weight: 800; color: #1a202c; font-size: 24px;">Dashboard</h2>
|
||||
<p style="color: #718096; font-size: 14px; margin-top: 4px;">Overview of your branch</p>
|
||||
@ -40,29 +80,34 @@
|
||||
<div style="position: relative;">
|
||||
<input type="text" placeholder="Search leads, activities....." style="background:white; padding:12px 20px; border-radius:10px; border:1px solid #e2e8f0; width: 350px; font-size: 13px;">
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="stat-cards">
|
||||
<div class="card">
|
||||
<div class="stat-val">4</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $total_leads; ?></div>
|
||||
<div class="stat-label">Total Leads</div>
|
||||
<div class="stat-change text-success">↑ 12% this month</div>
|
||||
<!-- <div class="stat-change text-success">↑ 12% this month</div> -->
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="stat-val">6</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $total_activities ?></div>
|
||||
<div class="stat-label">Total Activities</div>
|
||||
<div class="stat-change text-success">↑ 5% this month</div>
|
||||
<!-- <div class="stat-change text-success">↑ 5% this month</div> -->
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="stat-val">1</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo ($total_activities - $completed_acts) ?></div>
|
||||
<div class="stat-label">Pending Activities</div>
|
||||
<!-- <div class="stat-change text-success">↑ 15% this month</div> -->
|
||||
</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $completed_acts ?></div>
|
||||
<div class="stat-label">Completed Activities</div>
|
||||
<div class="stat-change text-success">↑ 15% this month</div>
|
||||
<!-- <div class="stat-change text-success">↑ 15% this month</div> -->
|
||||
</div>
|
||||
<div class="card">
|
||||
<!-- <div class="card">
|
||||
<div class="stat-val">₹15.0L</div>
|
||||
<div class="stat-label">Pipeline Value</div>
|
||||
<div class="stat-change text-success">↑ 18% this month</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="main-grid">
|
||||
@ -81,62 +126,117 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Venba Infotech</strong></td>
|
||||
<td>
|
||||
<div style="color: #ff6b35; font-weight: 700; font-size: 11px;">CALL</div>
|
||||
<div style="font-size: 12px; color: #718096;">John Doe</div>
|
||||
</td>
|
||||
<td style="color: #4a5568; font-weight: 500;">20 Feb 2026</td>
|
||||
<td><span class="status-pill status-completed">Completed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Acme Corporation</strong></td>
|
||||
<td>
|
||||
<div style="color: #ff6b35; font-weight: 700; font-size: 11px;">EMAIL</div>
|
||||
<div style="font-size: 12px; color: #718096;">John Doe</div>
|
||||
</td>
|
||||
<td style="color: #4a5568; font-weight: 500;">20 Feb 2026</td>
|
||||
<td><span class="status-pill status-pending">Pending</span></td>
|
||||
</tr>
|
||||
<?php if (!empty($recent_acts)): ?>
|
||||
<?php foreach ($recent_acts as $a): ?>
|
||||
<?php
|
||||
$activityIcons = [
|
||||
'Call' => '📞',
|
||||
'Email' => '✉️',
|
||||
'Meeting' => '📅',
|
||||
'Visit' => '🚗',
|
||||
'Demo' => '🎬',
|
||||
'Share' => '📄',
|
||||
'Todo' => '✓'
|
||||
];
|
||||
$icon = $activityIcons[$a['activity_type']] ?? '📌';
|
||||
$statusClass = strtolower(str_replace(' ', '-', $a['status']));
|
||||
$formattedDate = date('d M Y', strtotime($a['scheduled_date']));
|
||||
?>
|
||||
<tr>
|
||||
<td><strong><?= esc($a['company_name']) ?></strong></td>
|
||||
<td>
|
||||
<div style="color: #ff6b35; font-weight: 700; font-size: 11px;">
|
||||
<?= $icon ?> <?= strtoupper(esc($a['activity_type'])) ?>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #718096;">
|
||||
<?= esc($a['assigned_to_name'] ?? 'ID: ' . $a['assigned_to']) ?>
|
||||
</div>
|
||||
</td>
|
||||
<td style="color: #4a5568; font-weight: 500;"><?= $formattedDate ?></td>
|
||||
<td><span class="status-pill status-<?= $statusClass ?>"><?= ucfirst(esc($a['status'])) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">✓</div> No activities found
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
|
||||
<h3 style="font-size: 16px; font-weight: 700; margin-bottom: 15px;">Sales Team Performance</h3>
|
||||
<div class="team-member">
|
||||
<div class="member-img" style="background: #ff6b35;">V</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 700; font-size: 14px;">Venkat 3.0</div>
|
||||
<div style="font-size: 11px; color: #718096;">Sales Manager</div>
|
||||
</div>
|
||||
<div class="act-stats">
|
||||
<div class="total">6 acts</div>
|
||||
<div class="done">1 done</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="team-member">
|
||||
<div class="member-img" style="background: #4a5568;">P</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 700; font-size: 14px;">Pavi_the_Staff V</div>
|
||||
<div style="font-size: 11px; color: #718096;">Sales Staff</div>
|
||||
</div>
|
||||
<div class="act-stats">
|
||||
<div class="total">0 acts</div>
|
||||
<div class="done">0 done</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php foreach ($team as $member) {
|
||||
$firstLetter = strtoupper(substr($member['first_name'], 0, 1));
|
||||
$fullName = $member['first_name'] . ' ' . $member['last_name'];
|
||||
$totalActs = $member['total_acts'];
|
||||
$doneActs = $member['done_acts'];
|
||||
$role = $member['role']; // fix your DB first
|
||||
|
||||
// Random or consistent color based on name
|
||||
$colors = ['#ff6b35', '#667eea', '#48bb78', '#ed8936', '#9f7aea'];
|
||||
$colorIndex = abs(crc32($member['first_name'])) % count($colors);
|
||||
$color = $colors[$colorIndex];
|
||||
|
||||
echo "
|
||||
<div class='team-member'>
|
||||
<div class='member-img' style='background: {$color};'>{$firstLetter}</div>
|
||||
<div style='flex: 1;'>
|
||||
<div style='font-weight: 700; font-size: 14px;'>{$fullName}</div>
|
||||
<div style='font-size: 11px; color: #718096;'>{$role}</div>
|
||||
</div>
|
||||
<div class='act-stats'>
|
||||
<div class='total'>{$totalActs} acts</div>
|
||||
<div class='done'>{$doneActs} done</div>
|
||||
</div>
|
||||
</div>";
|
||||
} ?>
|
||||
|
||||
|
||||
<div class="card breakdown-card">
|
||||
<h3 style="font-size: 15px; font-weight: 700; margin-bottom: 20px;">Activity Breakdown</h3>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #ff6b35;"></span> Call</span><strong>42%</strong></div>
|
||||
<!-- <div class="breakdown-item"><span><span class="dot" style="background: #ff6b35;"></span> Call</span><strong>42%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #48bb78;"></span> Email</span><strong>25%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #4299e1;"></span> Meeting</span><strong>18%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #ecc94b;"></span> Visit</span><strong>10%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #9f7aea;"></span> Demo</span><strong>5%</strong></div>
|
||||
<div class="breakdown-item"><span><span class="dot" style="background: #9f7aea;"></span> Demo</span><strong>5%</strong></div> -->
|
||||
<?php
|
||||
$activityConfig = [
|
||||
'Call' => ['color' => '#ff6b35', 'icon' => '📞'],
|
||||
'Email' => ['color' => '#48bb78', 'icon' => '✉️'],
|
||||
'Meeting' => ['color' => '#4299e1', 'icon' => '📅'],
|
||||
'Visit' => ['color' => '#ecc94b', 'icon' => '🚗'],
|
||||
'Demo' => ['color' => '#9f7aea', 'icon' => '🎬'],
|
||||
'Share' => ['color' => '#ed8936', 'icon' => '📄'],
|
||||
'Todo' => ['color' => '#718096', 'icon' => '✓'],
|
||||
];
|
||||
?>
|
||||
|
||||
<?php if (!empty($activity_breakdown)): ?>
|
||||
<?php foreach ($activity_breakdown as $item): ?>
|
||||
<?php
|
||||
$type = $item['activity_type'];
|
||||
$color = $activityConfig[$type]['color'] ?? '#718096';
|
||||
$icon = $activityConfig[$type]['icon'] ?? '📌';
|
||||
?>
|
||||
<div class="breakdown-item">
|
||||
<span>
|
||||
<span class="dot" style="background: <?= $color ?>;"></span>
|
||||
<?= $icon ?> <?= esc($type) ?>
|
||||
</span>
|
||||
<strong><?= $item['percentage'] ?>%</strong>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="empty-state">No activities found</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -157,20 +257,24 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Venba Infotech</strong></td>
|
||||
<td><span style="background: #edf2f7; color: #4a5568; font-size: 10px; padding: 3px 10px; border-radius: 12px; font-weight: 700; text-transform: uppercase;">New</span></td>
|
||||
<td style="color: #4a5568;">John Doe</td>
|
||||
<td><div style="text-align: center; font-weight: 700;">1</div></td>
|
||||
<td><div style="text-align: center; font-weight: 700;">1</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Acme Corporation</strong></td>
|
||||
<td><span style="background: #fff3e0; color: #f57c00; font-size: 10px; padding: 3px 10px; border-radius: 12px; font-weight: 700; text-transform: uppercase;">Potential</span></td>
|
||||
<td style="color: #4a5568;">John Doe</td>
|
||||
<td><div style="text-align: center; font-weight: 700;">1</div></td>
|
||||
<td><div style="text-align: center; font-weight: 700;">0</div></td>
|
||||
</tr>
|
||||
<?php if (!empty($leads_overview)): ?>
|
||||
<?php foreach ($leads_overview as $lead): ?>
|
||||
<?php $statusClass = strtolower(str_replace(' ', '-', $lead['status'])); ?>
|
||||
<tr>
|
||||
<td><strong><?= esc($lead['company_name']) ?></strong></td>
|
||||
<td><span class="status-pill status-<?= $statusClass ?>"><?= esc($lead['status']) ?></span></td>
|
||||
<td><?= esc($lead['assigned_to'] ?? 'Unassigned') ?></td>
|
||||
<td><div style="text-align: center; font-weight: 700;"><?= $lead['activities'] ?></div></td>
|
||||
<td><div style="text-align: center; font-weight: 700;"><?= $lead['opportunities'] ?></div></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="empty-state">No leads found</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<style>
|
||||
.my-dash { padding: 25px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #fff; }
|
||||
.my-dash { padding: 25px; margin-top: -21px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #fff; }
|
||||
.target-card { background: #1e252b; color: white; padding: 30px; border-radius: 15px; position: relative; margin-bottom: 30px; }
|
||||
.target-amount { font-size: 32px; font-weight: bold; margin: 10px 0; }
|
||||
.progress-container { background: #333; height: 8px; border-radius: 4px; margin: 20px 0; overflow: hidden; position: relative; }
|
||||
@ -10,7 +10,7 @@
|
||||
.stat-box .num { font-size: 20px; font-weight: bold; display: block; margin-bottom: 2px; }
|
||||
.stat-box .lbl { font-size: 11px; color: #999; }
|
||||
|
||||
.chart-circle { position: absolute; right: 40px; top: 35px; width: 75px; height: 75px; border: 6px solid #333; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 18px; border-top-color: #ff6b35; }
|
||||
/* .chart-circle { position: absolute; right: 40px; top: 35px; width: 75px; height: 75px; border: 6px solid #333; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 18px; border-top-color: #ff6b35; } */
|
||||
|
||||
.dashboard-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 25px; }
|
||||
.list-card { border: 1px solid #f0f0f0; border-radius: 12px; padding: 25px; background: #fff; }
|
||||
@ -19,31 +19,73 @@
|
||||
|
||||
.lead-initial { width: 35px; height: 35px; background: #f0f0f0; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #ff6b35; }
|
||||
.status-pill { font-size: 10px; padding: 2px 10px; border-radius: 12px; font-weight: 600; }
|
||||
|
||||
/* 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: 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; }
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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); }
|
||||
.btn-complete { background: #4caf50; color: white; border: none;padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;}
|
||||
.btn-complete:hover { background: #4caf50; transform: translateY(-1px); }
|
||||
.btn-view { background: #f0f0f0; color: #666; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;}
|
||||
.btn-view:hover { background: #f0f0f0; transform: translateY(-1px); }
|
||||
|
||||
</style>
|
||||
|
||||
<div class="my-dash">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:25px;">
|
||||
<div>
|
||||
<h1 style="font-size:22px; margin:0; font-weight: 700;">My Dashboard</h1>
|
||||
<p style="color:#666; font-size:13px; margin-top: 4px;">Welcome back, <?= $user_name ?></p>
|
||||
<!-- <h1 style="font-size:22px; margin:0; font-weight: 700;">My Dashboard</h1>
|
||||
<p style="color:#666; font-size:13px; margin-top: 4px;">Welcome back, <?= $user_name ?></p> -->
|
||||
</div>
|
||||
<div style="position: relative;">
|
||||
<input type="text" placeholder="Search leads, activities..." style="background:#f5f5f5; padding:10px 20px; border-radius:8px; border:none; width: 250px; font-size: 13px;">
|
||||
<select id="financial_year" name="financial_year" onchange="onFinancialYearChange(this)" style="background:#f5f5f5; padding:10px 20px; border-radius:8px; border:none; width: 250px; font-size: 13px; cursor: pointer;">
|
||||
<?php if(!empty($fin_years)): ?>
|
||||
<?php foreach($fin_years as $year): ?>
|
||||
<option value="<?= $year; ?>"><?= $year; ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="target-card">
|
||||
<div style="font-size:11px; color:#999; letter-spacing: 1px; font-weight: 600;">YEARLY TARGET 2025</div>
|
||||
<div style="font-size:11px; color:#999; letter-spacing: 1px; font-weight: 600;">YEARLY TARGET</div>
|
||||
<div class="target-amount">₹<?= number_format($target / 100000, 1) ?>L</div>
|
||||
<div style="font-size:12px; color:#777;">Apr 2025 - Mar 2026</div>
|
||||
<div style="font-size:12px; color:#777;"><?= $display_fin_years ?></div>
|
||||
|
||||
<div class="chart-circle">
|
||||
<span style="color:#ff6b35"><?= $percent ?>%</span>
|
||||
<div class="chart-circle" style="position: absolute; right: 40px; top: 35px; width: 75px; height: 75px;">
|
||||
<svg width="75" height="75" viewBox="0 0 75 75" style="transform: rotate(-90deg);">
|
||||
<circle cx="37.5" cy="37.5" r="30" fill="none" stroke="#333" stroke-width="6"/>
|
||||
<circle cx="37.5" cy="37.5" r="30" fill="none" stroke="#ff6b35" stroke-width="6"
|
||||
stroke-dasharray="<?= round(2 * M_PI * 30 * $percent / 100, 2) ?> 188.5"
|
||||
stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:#ff6b35; font-weight:bold; font-size:14px;"><?= $percent ?>%</span>
|
||||
</div>
|
||||
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" style="width: <?= $percent ?>%"></div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; justify-content:space-between; font-size:12px; color: #999;">
|
||||
<span>Achieved <strong style="color:white">₹<?= number_format($achieved / 100000, 1) ?>L</strong></span>
|
||||
<span>Remaining <strong style="color:white">₹<?= number_format($remaining / 100000, 1) ?>L</strong></span>
|
||||
@ -69,7 +111,7 @@
|
||||
<div style="font-weight:600; font-size:14px; color: #333;"><?= $u['company_name'] ?></div>
|
||||
<div style="font-size:11px; color:#999; margin-top: 3px;"><?= strtoupper($u['activity_type'] ?? 'EMAIL') ?> - <?= date('d Mar Y', strtotime($u['scheduled_date'])) ?></div>
|
||||
</div>
|
||||
<button class="badge-done">✓ Done</button>
|
||||
<button class="badge-done" onclick="openComp(<?= $u['activity_id'] ?>,<?= $u['lead_id'] ?>)">✓ Mark as completed</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
@ -90,4 +132,143 @@
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="completeModal">
|
||||
<div class="modal-content" style="max-width: 675px;">
|
||||
<div class="modal-header">
|
||||
<h3>Complete Activity</h3>
|
||||
<button class="btn-primary" style="background:none; color:#666; font-size:24px;" title="Close" onclick="closeModal('completeModal')">×</button>
|
||||
</div>
|
||||
<form class="parsley-examples" id="completeForm" enctype="multipart/form-data">
|
||||
<hr class="my-0">
|
||||
<div class="modal-body p-4">
|
||||
|
||||
<input type="hidden" id="comp_id">
|
||||
<input type="hidden" id="lead_id">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Outcome / Notes <span class="text-danger">*</span></label>
|
||||
<textarea id="comp_notes" class="search-input" style="width:100%; height:80px;" required placeholder="What happened? Any next steps?" rows="3" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="form-group text-right mb-0">
|
||||
<button type="button" class="btn-primary" style="background:#eee; color:#333; margin-right:10px;" onclick="closeModal('completeModal')">Cancel</button>
|
||||
<button type="submit" class="btn-primary">Submit Outcome</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
const API = '<?= base_url('sales') ?>';
|
||||
function openModal(id) { document.getElementById(id).classList.add('active'); }
|
||||
|
||||
function openComp(id, lead_id) {
|
||||
|
||||
document.getElementById('completeForm').reset();
|
||||
document.getElementById('comp_id').value = id;
|
||||
document.getElementById('lead_id').value = lead_id;
|
||||
openModal('completeModal');
|
||||
}
|
||||
document.getElementById('completeForm').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 1. Grab button variables first, DO NOT disable yet
|
||||
const submitBtn = e.target.querySelector('button[type="submit"]');
|
||||
const originalBtnText = submitBtn.innerText;
|
||||
|
||||
// 2. Capture values into variables
|
||||
const activityId = document.getElementById('comp_id').value;
|
||||
const leadId = document.getElementById('lead_id').value;
|
||||
|
||||
// This creates a TRUE or FALSE boolean
|
||||
let compNotes = document.getElementById('comp_notes').value.trim();
|
||||
|
||||
// --- VALIDATION CHECKS (Button is still normal here) ---
|
||||
if (compNotes === '') {
|
||||
toastr.warning('Completion notes is required');
|
||||
return;
|
||||
}
|
||||
|
||||
// --- ALL VALIDATION PASSED: Now disable button ---
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerText = 'Submitting...';
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
completion_notes: compNotes, // Use the already trimmed variable
|
||||
create_followup: 'no',
|
||||
};
|
||||
|
||||
// Mark current activity complete
|
||||
const res = await fetch(`${API}/activities/${activityId}/complete`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
toastr.success('Updated Successfully');
|
||||
// Final Actions
|
||||
closeModal('completeModal');
|
||||
|
||||
e.target.reset();
|
||||
|
||||
window.location.reload();
|
||||
|
||||
} else {
|
||||
let err = await res.json();
|
||||
if (res.status === 400) {
|
||||
let errorMessages = "";
|
||||
let seenMessages = [];
|
||||
|
||||
if (err.messages) {
|
||||
Object.entries(err.messages).forEach(([field, message]) => {
|
||||
if (!seenMessages.includes(message)) {
|
||||
errorMessages += `• ${message}<br>`;
|
||||
seenMessages.push(message);
|
||||
}
|
||||
});
|
||||
toastr.error(errorMessages, 'Validation Error', { allowHtml: true });
|
||||
} else {
|
||||
toastr.warning(err.message || 'Validation failed', 'Warning');
|
||||
}
|
||||
} else {
|
||||
toastr.error(err.message || 'Error completing activity');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
toastr.error("A network error occurred.");
|
||||
} finally {
|
||||
// ALWAYS runs to reset button
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerText = originalBtnText;
|
||||
}
|
||||
};
|
||||
|
||||
function closeModal(id) {
|
||||
const modal = document.getElementById(id);
|
||||
if (modal) {
|
||||
modal.classList.remove('active'); // Hide the modal
|
||||
const form = modal.querySelector('form'); // Find the form inside this specific modal
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const fy = params.get('fy');
|
||||
if (fy) {
|
||||
document.getElementById('financial_year').value = fy;
|
||||
}
|
||||
});
|
||||
|
||||
function onFinancialYearChange(select) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('fy', select.value);
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
BIN
public/assets/images/sales_tracker_dark_sb.png
Normal file
BIN
public/assets/images/sales_tracker_dark_sb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Loading…
Reference in New Issue
Block a user