Merge branch 'dev' of bitbucket.org:jubilian/nhance into dev
This commit is contained in:
commit
79150ca5ae
@ -11,10 +11,11 @@ $routes->options('(:any)', function() {
|
||||
});
|
||||
|
||||
$routes->group('sheet', function ($routes) {
|
||||
$routes->get('(:any)', 'GoogleSheetController::editor/$1');
|
||||
$routes->get('(:any)/fetch', 'GoogleSheetController::fetch/$1');
|
||||
$routes->post('(:any)/save', 'GoogleSheetController::save/$1');
|
||||
$routes->get('(:any)/download', 'GoogleSheetController::download/$1');
|
||||
$routes->post('create', 'GoogleSheetController::create');
|
||||
$routes->get('(:segment)/fetch', 'GoogleSheetController::fetch/$1');
|
||||
$routes->post('(:segment)/save', 'GoogleSheetController::save/$1');
|
||||
$routes->get('(:segment)/download', 'GoogleSheetController::download/$1');
|
||||
$routes->get('(:segment)', 'GoogleSheetController::editor/$1');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -6,6 +6,35 @@ use CodeIgniter\Controller;
|
||||
class GoogleSheetController extends Controller
|
||||
{
|
||||
protected GoogleSheetLib $sheetLib;
|
||||
public $config = [
|
||||
|
||||
'permissions' => [
|
||||
'editors' => [
|
||||
'vitvelz@gmail.com', 'velz1990@gmail.com','venkateshraman786@gmail.com'
|
||||
// 'group:rfq-editors@company.com'
|
||||
],
|
||||
'viewers' => [],
|
||||
],
|
||||
|
||||
'protections' => [
|
||||
[
|
||||
'range' => 'RFQ Page!B12:C12',
|
||||
'users' => [ //allowed users to edit this cell range
|
||||
'velz1990@gmail.com','vitvelz@gmail.com','firebase-adminsdk-mcdfe@nhance-ee8d1.iam.gserviceaccount.com'
|
||||
],
|
||||
'groups' => []
|
||||
],
|
||||
[
|
||||
'range' => 'Claims Page!A1',
|
||||
'users' => [
|
||||
'velz1990@gmail.com','venkateshraman786@gmail.com','firebase-adminsdk-mcdfe@nhance-ee8d1.iam.gserviceaccount.com'
|
||||
],
|
||||
'groups' => []
|
||||
]
|
||||
],
|
||||
|
||||
'parentFolderId' => '19uySI-PSFQfFpZvMSnBtirXbwfCTXFBL', // RFQ folder
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -23,10 +52,16 @@ class GoogleSheetController extends Controller
|
||||
/* ---------- FETCH ---------- */
|
||||
public function fetch(string $sheetId)
|
||||
{
|
||||
// $data = $this->sheetLib->read($sheetId);
|
||||
$data = $this->sheetLib->read($sheetId);
|
||||
// print_rr($data);die;
|
||||
// return $this->response->setJSON($data);
|
||||
echo 'Hi';
|
||||
return $this->response->setJSON($data);
|
||||
// echo 'Hi';
|
||||
}
|
||||
|
||||
public function sample(string $sheetId)
|
||||
{
|
||||
echo $sheetId;
|
||||
|
||||
}
|
||||
|
||||
/* ---------- SAVE ---------- */
|
||||
@ -63,4 +98,39 @@ class GoogleSheetController extends Controller
|
||||
)
|
||||
->setBody($content);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
try {
|
||||
$config = config('RfqConfig');
|
||||
// #$lib = new RfqGoogleSheetLib();
|
||||
|
||||
// $templateId = $this->request->getPost('templateId');
|
||||
// if (!$templateId) {
|
||||
// throw new \Exception('Template ID missing');
|
||||
// }
|
||||
|
||||
// $newSheetId = $this->sheetLib->copyTemplate(
|
||||
// $templateId,
|
||||
// 'RFQ_' . date('Ymd_His'),
|
||||
// $this->config['parentFolderId']
|
||||
// );
|
||||
$newSheetId = '1GXNDNXoWriClb5HCqPYd2GAY1T8aie_Hos0yAOoTaC0';
|
||||
// $this->sheetLib->applyPermissions($newSheetId, $this->config['permissions']);
|
||||
$this->sheetLib->applyProtections($newSheetId, $this->config['protections']);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 'success',
|
||||
'url' => $this->sheetLib->sheetUrl($newSheetId)
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return $this->response
|
||||
->setStatusCode(500)
|
||||
->setJSON([
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,8 +302,8 @@ class LeadsController extends BaseController
|
||||
|
||||
public function viewLeadsList()
|
||||
{
|
||||
$data['tab_name'] = 'Leads';
|
||||
$data['page_name'] = 'Leads';
|
||||
$data['tab_name'] = 'Opportunities';
|
||||
$data['page_name'] = 'Opportunities';
|
||||
|
||||
// Set basic data
|
||||
$data['issuer'] = $this->issuer;
|
||||
@ -372,7 +372,7 @@ class LeadsController extends BaseController
|
||||
|
||||
'lead_type' => [
|
||||
'rules' => 'integer',
|
||||
'errors' => ['required' => 'Lead Type is required']
|
||||
'errors' => ['required' => 'Opportunity Type is required']
|
||||
],
|
||||
'issuer' => [
|
||||
'rules' => 'required',
|
||||
@ -934,10 +934,10 @@ class LeadsController extends BaseController
|
||||
}
|
||||
|
||||
if (count($insertCount) > 0) {
|
||||
return $this->respond(['status' => true, 'lead_id' => $insert, 'message' => 'New Lead created successfully', 'data' => $data], 200);
|
||||
return $this->respond(['status' => true, 'lead_id' => $insert, 'message' => 'New Opportunity created successfully', 'data' => $data], 200);
|
||||
}
|
||||
|
||||
return $this->respond(['status' => false, 'lead_id' => $insert, 'message' => "Failed to create Lead", 'data' => $data], 200);
|
||||
return $this->respond(['status' => false, 'lead_id' => $insert, 'message' => "Failed to create Opportunity", 'data' => $data], 200);
|
||||
}
|
||||
|
||||
private function updateOldLead($id, $data)
|
||||
@ -946,9 +946,9 @@ class LeadsController extends BaseController
|
||||
|
||||
$this->insertMultiFilesData($data[0]['multi_file_data'], $id, $data[0]['lead_form_type']);
|
||||
$this->insertLeadStatus($id, $data[0]['status'], 3);
|
||||
return $this->respond(['status' => true, 'lead_id' => $id, 'message' => "Lead updated successfully", 'data' => $data], 200);
|
||||
return $this->respond(['status' => true, 'lead_id' => $id, 'message' => "Opportunity updated successfully", 'data' => $data], 200);
|
||||
}
|
||||
return $this->respond(['status' => false, 'lead_id' => $id, 'message' => "Failed to update Lead", 'data' => $data], 200);
|
||||
return $this->respond(['status' => false, 'lead_id' => $id, 'message' => "Failed to update Opportunity", 'data' => $data], 200);
|
||||
}
|
||||
|
||||
// Get the Single Lead data for edit uisng ajax (do not delete)
|
||||
@ -1305,7 +1305,7 @@ class LeadsController extends BaseController
|
||||
|
||||
if (!$lead_data) {
|
||||
// Handle case where lead doesn't exist
|
||||
throw new \Exception('Lead not found');
|
||||
throw new \Exception('Opportunity not found');
|
||||
}
|
||||
|
||||
// 4. Conditional query - only fetch if needed
|
||||
@ -1494,7 +1494,7 @@ class LeadsController extends BaseController
|
||||
$lead_id = $data['lead_id'] ?? null;
|
||||
|
||||
if (!$lead_id) {
|
||||
return $this->respond(['status' => false, 'message' => 'Lead ID is required'], 400);
|
||||
return $this->respond(['status' => false, 'message' => 'Opportunity ID is required'], 400);
|
||||
}
|
||||
|
||||
$rfq_created = $this->RFQModel->where('lead_id', $lead_id)->where('is_active', 1)->countAllResults();
|
||||
@ -2796,7 +2796,7 @@ class LeadsController extends BaseController
|
||||
$file_name_with_path = WRITEPATH . "/uploads/lead_files/" . $lead_data['file_name'];
|
||||
|
||||
if (!$lead_data) {
|
||||
return ['status' => 'failed', 'message' => 'Lead data not found'];
|
||||
return ['status' => 'failed', 'message' => 'Opportunity data not found'];
|
||||
}
|
||||
|
||||
try{
|
||||
@ -5583,7 +5583,7 @@ class LeadsController extends BaseController
|
||||
// dd($file_name_with_path);
|
||||
|
||||
if (!$lead_data) {
|
||||
return ['status' => 'failed', 'message' => 'Lead data not found'];
|
||||
return ['status' => 'failed', 'message' => 'Opportunity data not found'];
|
||||
}
|
||||
|
||||
if ($lead_data['file_name']) {
|
||||
@ -6579,7 +6579,7 @@ class LeadsController extends BaseController
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'code' => 400,
|
||||
'message' => 'Lead ID is required'
|
||||
'message' => 'Opportunity ID is required'
|
||||
], 400);
|
||||
}
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ class SalesController extends BaseController
|
||||
|
||||
}
|
||||
// Otherwise fetch ALL sales managers in this branch
|
||||
elseif (in_array($role,[1,2,3,4,5])) {
|
||||
elseif (in_array($role,[1,5])) {
|
||||
|
||||
$data['users'] = $db->table('user_profiles up')
|
||||
->select('up.id, up.first_name, up.last_name, up.nhance_branch_id')
|
||||
@ -793,7 +793,7 @@ class SalesController extends BaseController
|
||||
|
||||
// dd($logged_user_id, $nhance_branch_id, $role, $team_id );
|
||||
|
||||
if (in_array($role,[5])) {
|
||||
if (in_array($role,[1,5])) {
|
||||
|
||||
$sales_manager_ids = array_column(
|
||||
$db->table('user_profiles up')
|
||||
@ -822,36 +822,46 @@ class SalesController extends BaseController
|
||||
|
||||
public function branchLevelDashboard($branchId,$sales_manager_ids)
|
||||
{
|
||||
$data['tab_name'] = 'Dashboard';
|
||||
$data['page_name'] = 'Dashboard';
|
||||
// Hardcoded branch ID as requested
|
||||
// $branchId = 1;
|
||||
|
||||
try {
|
||||
// 1. Lead Statistics
|
||||
$stats = $this->leadModel->getLeadStats(); // Using existing model method
|
||||
|
||||
// 2. Activity Statistics
|
||||
$activityStats = [
|
||||
'total' => $this->activityModel->countAllResults(),
|
||||
'completed' => $this->activityModel->where('status', 'completed')->countAllResults(),
|
||||
'pending' => $this->activityModel->where('status', 'pending')->countAllResults(),
|
||||
];
|
||||
|
||||
// 3. Team Performance (Aggregating activity counts per user)
|
||||
$db = \Config\Database::connect();
|
||||
// Final safe check
|
||||
if (empty($sales_manager_ids)) {
|
||||
$sales_manager_ids = array_values(array_map('intval', $sales_manager_ids));
|
||||
|
||||
// Final safe check
|
||||
if (empty($sales_manager_ids)) {
|
||||
// No valid IDs — skip queries or return empty
|
||||
$teamPerformance = [];
|
||||
$recentActivities = [];
|
||||
$leadsOverview = [];
|
||||
} else {
|
||||
$total_leads = 0;
|
||||
$total_activity = 0;
|
||||
$total_completed_activity = 0;
|
||||
$total_pending_activity = 0;
|
||||
$pending_activities = [];
|
||||
$recent_activities = [];
|
||||
$teamPerformance = [];
|
||||
|
||||
$leadsOverview = [];
|
||||
} else {
|
||||
|
||||
if (empty($sales_manager_ids) || !is_array($sales_manager_ids)) {
|
||||
$sales_manager_ids = array_filter((array) $sales_manager_ids); // removes null, "", 0
|
||||
}
|
||||
if (empty($sales_manager_ids) || !is_array($sales_manager_ids)) {
|
||||
$sales_manager_ids = array_filter((array) $sales_manager_ids); // removes null, "", 0
|
||||
}
|
||||
|
||||
// 1. Lead Statistics
|
||||
$total_leads = $this->leadModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults(); // Use countAllResults, NOT countAll
|
||||
// echo $this->leadModel->getLastQuery();die();
|
||||
|
||||
// 2. Total activity
|
||||
$total_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults();
|
||||
|
||||
// 3. Completed activity
|
||||
$total_completed_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'completed')->countAllResults();
|
||||
|
||||
// 4. Pending activity
|
||||
$total_pending_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'pending')->countAllResults();
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// 5. Team Performance
|
||||
$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,
|
||||
@ -861,25 +871,25 @@ class SalesController extends BaseController
|
||||
->where('u.is_active', 1)
|
||||
->get()->getResultArray();
|
||||
|
||||
// 4. Recent Activities (Joining for Lead Names)
|
||||
$recentActivities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
|
||||
// 6. Recent Activities (Joining for Lead Names)
|
||||
$recent_activities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name')
|
||||
->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id')
|
||||
->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')
|
||||
// 7. Pending Activities (List)
|
||||
$pending_activities = $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,sa.notes')
|
||||
->join('user_profiles up', 'up.id = sa.assigned_to', 'left')
|
||||
->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left') // ✅ ADD THIS
|
||||
->orderBy('sa.scheduled_date', 'DESC')
|
||||
->limit(6)
|
||||
->whereIn('sa.assigned_to', $sales_manager_ids)
|
||||
->where('sa.status', 'pending')
|
||||
->get()->getResultArray();
|
||||
|
||||
// 5. All Leads Overview
|
||||
$leadsOverview = $db->table('sales_actual_leads sal')
|
||||
// 8. 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
|
||||
@ -888,39 +898,34 @@ class SalesController extends BaseController
|
||||
->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
|
||||
// ->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();
|
||||
|
||||
// 9. Activity BrakDown
|
||||
$activityBreakdown = $db->table('sales_activities')
|
||||
->select("activity_type, COUNT(*) AS total, ROUND(COUNT(*) * 100.0 / {$total}, 0) AS percentage", false)
|
||||
->select("activity_type, COUNT(*) AS total, ROUND(COUNT(*) * 100.0 / {$total_activity}, 0) AS percentage", false)
|
||||
->whereIn('assigned_to', $sales_manager_ids)
|
||||
->groupBy('activity_type')
|
||||
->orderBy('total', 'DESC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$teamPerformance = array_filter($teamPerformance, function($row) {
|
||||
return ($row['total_acts'] + $row['done_acts']) > 0;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
$data = [
|
||||
'total_leads' => $stats['total'],
|
||||
'total_activities' => $activityStats['total'],
|
||||
'completed_acts' => $activityStats['completed'],
|
||||
'pipeline_value' => '15.0L', // Hardcoded placeholder from PDF [cite: 14]
|
||||
'team' => $teamPerformance,
|
||||
'recent_acts' => $recentActivities,
|
||||
'leads_overview' => $leadsOverview,
|
||||
'total_leads' => $total_leads,
|
||||
'total_acts' => $total_activity,
|
||||
'total_completed_acts' => $total_completed_activity,
|
||||
'total_pending_acts'=> $total_pending_activity,
|
||||
'pipeline_value' => '15.0L', // Hardcoded placeholder from PDF [cite: 14]
|
||||
'team' => $teamPerformance,
|
||||
'recent_acts' => $recent_activities,
|
||||
'pending_acts' => $pending_activities,
|
||||
'leads_overview' => $leadsOverview,
|
||||
'activity_breakdown'=> $activityBreakdown,
|
||||
'tab_name' => "Sales Dashboard",
|
||||
'page_name' => "Sales Dashboard"
|
||||
'tab_name' => "Sales Dashboard",
|
||||
'page_name' => "Sales Dashboard"
|
||||
];
|
||||
|
||||
// dd($data);
|
||||
|
||||
@ -1002,7 +1002,7 @@ class TestingController extends BaseController
|
||||
// 'dashboard' => 1
|
||||
'dashboard' => $database_id
|
||||
],
|
||||
'exp' => time() + (10 * 60) // 10 minutes
|
||||
'exp' => time() + (10 * 60), // 10 minutes
|
||||
];
|
||||
|
||||
if(!empty($policy_id)){
|
||||
|
||||
@ -4,6 +4,7 @@ use Google_Client;
|
||||
use Google_Service_Sheets;
|
||||
use Google_Service_Drive;
|
||||
use Google_Service_Sheets_ValueRange;
|
||||
use Google_Service_Sheets_BatchUpdateSpreadsheetRequest;
|
||||
|
||||
class GoogleSheetLib
|
||||
{
|
||||
@ -11,13 +12,14 @@ class GoogleSheetLib
|
||||
protected Google_Service_Sheets $sheets;
|
||||
protected Google_Service_Drive $drive;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Google_Client();
|
||||
|
||||
// Service account JSON
|
||||
$this->client->setAuthConfig(
|
||||
ROOTPATH . 'gdrive-demo-394007-5b1d856b0c5b.json'
|
||||
ROOTPATH . 'nhance-ee8d1-e3c5269b1ec7.json'
|
||||
);
|
||||
|
||||
// IMPORTANT for service account
|
||||
@ -32,11 +34,12 @@ class GoogleSheetLib
|
||||
// Init services
|
||||
$this->sheets = new Google_Service_Sheets($this->client);
|
||||
$this->drive = new Google_Service_Drive($this->client);
|
||||
|
||||
}
|
||||
|
||||
/* ===================== READ ===================== */
|
||||
|
||||
public function read(string $spreadsheetId, string $range = 'Sheet1')
|
||||
public function read(string $spreadsheetId, string $range = 'RFQ Page')
|
||||
{
|
||||
$response = $this->sheets
|
||||
->spreadsheets_values
|
||||
@ -77,4 +80,205 @@ class GoogleSheetLib
|
||||
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
|
||||
|
||||
/* ================= COPY TEMPLATE ================= */
|
||||
|
||||
public function copyTemplate(string $templateId, string $name, string $folderId): string
|
||||
{
|
||||
$file = $this->drive->files->copy(
|
||||
$templateId,
|
||||
new \Google_Service_Drive_DriveFile([
|
||||
'name' => $name,
|
||||
'parents' => [$folderId],
|
||||
|
||||
]),[
|
||||
'supportsAllDrives' => true,
|
||||
'fields' => 'id, name, parents'
|
||||
]
|
||||
);
|
||||
|
||||
return $file->id;
|
||||
}
|
||||
|
||||
/* ================= PERMISSIONS ================= */
|
||||
|
||||
public function applyPermissions(string $fileId, array $permissions)
|
||||
{
|
||||
foreach ($permissions['editors'] as $email) {
|
||||
$this->createPermission($fileId, $email, 'writer');
|
||||
}
|
||||
|
||||
foreach ($permissions['viewers'] as $email) {
|
||||
$this->createPermission($fileId, $email, 'reader');
|
||||
}
|
||||
}
|
||||
|
||||
private function createPermission(string $fileId, string $email, string $role)
|
||||
{
|
||||
$type = str_starts_with($email, 'group:') ? 'group' : 'user';
|
||||
$email = str_replace('group:', '', $email);
|
||||
|
||||
$this->drive->permissions->create(
|
||||
$fileId,
|
||||
new \Google_Service_Drive_Permission([
|
||||
'type' => $type,
|
||||
'role' => $role,
|
||||
'emailAddress' => $email
|
||||
]),
|
||||
['sendNotificationEmail' => false,'supportsAllDrives' => true]
|
||||
);
|
||||
}
|
||||
|
||||
/* ================= PROTECTIONS ================= */
|
||||
|
||||
public function applyProtectionsold(string $spreadsheetId, array $ranges)
|
||||
{
|
||||
$spreadsheet = $this->sheets->spreadsheets->get($spreadsheetId);
|
||||
$sheetId = $spreadsheet->getSheets()[0]->getProperties()->getSheetId();
|
||||
|
||||
$requests = [];
|
||||
|
||||
foreach ($ranges as $range) {
|
||||
[$sheetName, $a1] = explode('!', $range);
|
||||
|
||||
$requests[] = [
|
||||
'addProtectedRange' => [
|
||||
'protectedRange' => [
|
||||
'range' => [
|
||||
'sheetId' => $sheetId
|
||||
],
|
||||
'warningOnly' => false
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$this->sheets->spreadsheets->batchUpdate(
|
||||
$spreadsheetId,
|
||||
new Google_Service_Sheets_BatchUpdateSpreadsheetRequest([
|
||||
'requests' => $requests
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function applyProtections(string $spreadsheetId, array $protections)
|
||||
{
|
||||
// Fetch spreadsheet metadata
|
||||
$spreadsheet = $this->sheets->spreadsheets->get(
|
||||
$spreadsheetId,
|
||||
['fields' => 'sheets(properties(sheetId,title,gridProperties))']
|
||||
);
|
||||
|
||||
// Map sheet names
|
||||
$sheetMap = [];
|
||||
foreach ($spreadsheet->getSheets() as $sheet) {
|
||||
$props = $sheet->getProperties();
|
||||
$sheetMap[$props->getTitle()] = [
|
||||
'sheetId' => $props->getSheetId(),
|
||||
'rowCount' => $props->getGridProperties()->getRowCount(),
|
||||
'colCount' => $props->getGridProperties()->getColumnCount(),
|
||||
];
|
||||
}
|
||||
|
||||
$requests = [];
|
||||
|
||||
foreach ($protections as $protection) {
|
||||
|
||||
$rangeStr = $protection['range'];
|
||||
|
||||
if (!str_contains($rangeStr, '!')) {
|
||||
throw new \Exception("Invalid range format: {$rangeStr}");
|
||||
}
|
||||
|
||||
[$sheetName, $a1] = explode('!', $rangeStr, 2);
|
||||
|
||||
if (!isset($sheetMap[$sheetName])) {
|
||||
throw new \Exception("Sheet not found: {$sheetName}");
|
||||
}
|
||||
|
||||
$sheetMeta = $sheetMap[$sheetName];
|
||||
|
||||
$gridRange = $this->convertA1ToGridRange(
|
||||
$a1,
|
||||
$sheetMeta['sheetId'],
|
||||
$sheetMeta['rowCount'],
|
||||
$sheetMeta['colCount']
|
||||
);
|
||||
|
||||
$protectedRange = [
|
||||
'range' => $gridRange,
|
||||
'description' => 'RFQ Protected Area',
|
||||
'warningOnly' => false,
|
||||
'editors' => [
|
||||
'users' => $protection['users'] ?? [],
|
||||
'groups' => $protection['groups'] ?? []
|
||||
]
|
||||
];
|
||||
|
||||
$requests[] = [
|
||||
'addProtectedRange' => [
|
||||
'protectedRange' => $protectedRange
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($requests)) {
|
||||
$batch = new \Google_Service_Sheets_BatchUpdateSpreadsheetRequest([
|
||||
'requests' => $requests
|
||||
]);
|
||||
|
||||
$this->sheets->spreadsheets->batchUpdate($spreadsheetId, $batch);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ================= URL ================= */
|
||||
|
||||
public function sheetUrl(string $sheetId): string
|
||||
{
|
||||
return "https://docs.google.com/spreadsheets/d/{$sheetId}/edit";
|
||||
}
|
||||
|
||||
|
||||
private function convertA1ToGridRange($a1, $sheetId, $maxRows, $maxCols)
|
||||
{
|
||||
if (preg_match('/^([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?$/i', $a1, $m)) {
|
||||
|
||||
$startCol = $this->colToIndex($m[1]);
|
||||
$startRow = intval($m[2]) - 1;
|
||||
|
||||
if (!empty($m[3])) {
|
||||
$endCol = $this->colToIndex($m[3]) + 1;
|
||||
$endRow = intval($m[4]);
|
||||
} else {
|
||||
$endCol = $startCol + 1;
|
||||
$endRow = $startRow + 1;
|
||||
}
|
||||
|
||||
return [
|
||||
'sheetId' => $sheetId,
|
||||
'startRowIndex' => $startRow,
|
||||
'endRowIndex' => $endRow,
|
||||
'startColumnIndex' => $startCol,
|
||||
'endColumnIndex' => $endCol
|
||||
];
|
||||
}
|
||||
|
||||
throw new \Exception("Unsupported A1 format: {$a1}");
|
||||
}
|
||||
|
||||
private function colToIndex($letters)
|
||||
{
|
||||
$letters = strtoupper($letters);
|
||||
$index = 0;
|
||||
for ($i = 0; $i < strlen($letters); $i++) {
|
||||
$index = $index * 26 + (ord($letters[$i]) - 64);
|
||||
}
|
||||
return $index - 1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
<div class="form-group col-md-6">
|
||||
<label for="mobile">CD Account Number<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="cd_ac_no_for_cd_master" name="cd_ac_no" onkeypress="return onlyNumbers(event)" required data-parsley-type="digits">
|
||||
<small style="margin-left: 10px;font-size: 10px;"><span class="text-danger" id="cd_ac_no_for_cd_master_errorr"></span></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-6">
|
||||
@ -126,12 +127,15 @@
|
||||
|
||||
console.log(res)
|
||||
if(res.status == true){
|
||||
toastr.warning(res.message, 'warning');
|
||||
|
||||
$('#cd_ac_no_for_cd_master_errorr').text(res.message);
|
||||
// toastr.warning(res.message, 'warning');
|
||||
// $('#cd_ac_no').val('');
|
||||
$('#cd_master_btn_Submit').prop('disabled',true);
|
||||
return;
|
||||
}
|
||||
$('#cd_master_btn_Submit').prop('disabled',false);
|
||||
$('#cd_ac_no_for_cd_master_errorr').text('');
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error(xhr.responseText);
|
||||
|
||||
@ -16,6 +16,28 @@
|
||||
|
||||
<button onclick="save()">💾 Save</button>
|
||||
<button onclick="download()">⬇ Download</button>
|
||||
<button onclick="createRfq()">🚀 Create RFQ</button>
|
||||
|
||||
<div id="result" style="margin-top:15px;"></div>
|
||||
|
||||
<script>
|
||||
function createRfq() {
|
||||
fetch('<?php echo base_url() ?>' +'/sheet/create', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'templateId=1efHU8EBkjTZTzOx115xRrtgz3kwvvVp0p5GbrvREfiw'
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(res => {
|
||||
if (res.status === 'success') {
|
||||
document.getElementById('result').innerHTML =
|
||||
`<a href="${res.url}" target="_blank">Open RFQ Sheet</a>`;
|
||||
} else {
|
||||
alert(res.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<hr>
|
||||
|
||||
@ -26,9 +48,9 @@
|
||||
const sheetId = "<?= esc($sheetId) ?>";
|
||||
alert('second');
|
||||
/* ---------- LOAD ---------- */
|
||||
fetch('<?php echo base_url() ?>' + `/sheet/${sheetId}/fetch`)
|
||||
.then(r => r.json())
|
||||
fetch('<?php echo base_url() ?>' + `sheet/${sheetId}/fetch`)
|
||||
.then(r => r.json())
|
||||
// .then(r => r.json())
|
||||
.then(render);
|
||||
|
||||
function render(data) {
|
||||
|
||||
@ -2073,7 +2073,7 @@
|
||||
<a href="<?= base_url('/leads/list') ?>" class="img-inactive">
|
||||
<img
|
||||
src="<?= base_url() . "public"; ?>/assets/images/leads_sb.png" alt="Logo" height="20">
|
||||
<span> Leads </span>
|
||||
<span> Opportunities </span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-3">
|
||||
<label for="filter_lead_type"> Lead Type <span class="text-danger"></span></label>
|
||||
<label for="filter_lead_type"> Opportunity Type <span class="text-danger"></span></label>
|
||||
<select class="form-control" id="filter_lead_type" name="filter_lead_type">
|
||||
<option value="0">Select</option>
|
||||
<?php
|
||||
|
||||
@ -165,10 +165,10 @@ $isActive = get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID, user_
|
||||
<div class="leadTypeTile">
|
||||
<div class="card dash-card" onclick="hide_and_show_tile(1,1)">
|
||||
<div class="card-block">
|
||||
<h4 class="mb-3">Leads</h4>
|
||||
<h4 class="mb-3">opportunities</h4>
|
||||
<h2 ><span class="span-color"><?= isset($lead_data) ? $lead_data['total'] : 0 ?></span>
|
||||
</h2>
|
||||
<h6><span class="text-danger">Lead Tracker</span></h6>
|
||||
<h6><span class="text-danger">Opportunity Tracker</span></h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -124,7 +124,7 @@
|
||||
<a href="<?= base_url('leads/list') ?>" style="cursor: pointer;">
|
||||
<i class="mdi mdi-chevron-left" style="font-size: 43px;"></i>
|
||||
</a>
|
||||
<span class="title-text" id="page_title" style="position: relative;">Add Lead</span>
|
||||
<span class="title-text" id="page_title" style="position: relative;">Add Opportunity</span>
|
||||
</h4>
|
||||
</div>
|
||||
<form role="form" class="parsley-examples" method="post" id="leads_form_id"
|
||||
@ -139,9 +139,9 @@
|
||||
<div class="form-row">
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="lead_type">Lead Type<span class="text-danger">*</span></label>
|
||||
<label for="lead_type">Opportunity Type<span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="lead_type" name="lead_type" required>
|
||||
<option value="">Select Lead Type</option>
|
||||
<option value="">Select Opportunity Type</option>
|
||||
<?php
|
||||
if (isset($lead_type) && count($lead_type)) {
|
||||
foreach ($lead_type as $key => $value) {
|
||||
@ -739,7 +739,7 @@
|
||||
|
||||
hide_list_show_add();
|
||||
|
||||
var page_title = 'Edit Lead';
|
||||
var page_title = 'Edit Opportunity';
|
||||
|
||||
$('#page_title').text(page_title);
|
||||
$('#leads_primarykey').val(res.data.id);
|
||||
|
||||
@ -954,7 +954,7 @@ if (isset($selected_lead_type)) {
|
||||
|
||||
hide_list_show_add();
|
||||
|
||||
$('#page_title').text('Edit Lead');
|
||||
$('#page_title').text('Edit Opportunity');
|
||||
$('#leads_primarykey').val(data.id || '');
|
||||
$('#actual_lead_id').val(data.actual_lead_id || 0);
|
||||
$('#policy_start_date').val(data.policy_end_date || '');
|
||||
@ -1129,7 +1129,7 @@ if (isset($selected_lead_type)) {
|
||||
}
|
||||
}
|
||||
|
||||
$('#page_title').text('Edit Lead');
|
||||
$('#page_title').text('Edit Opportunity');
|
||||
$('#leads_primarykey').val(data.id || '');
|
||||
$('#actual_lead_id').val(data.actual_lead_id || 0);
|
||||
$('#policy_start_date').val(data.policy_end_date || '');
|
||||
|
||||
@ -151,7 +151,7 @@ table.dataTable tbody td {
|
||||
<div class="card-body">
|
||||
<div class="row" style="margin-bottom:1rem;">
|
||||
<div class="col-6" style="align-self: center;">
|
||||
<h4 style="position: relative;">Leads List</h4>
|
||||
<h4 style="position: relative;">Opportunities List</h4>
|
||||
</div>
|
||||
|
||||
<div class="col-3" id="status_change" style="text-align: right; position: relative;top: 56px; left: 291px;">
|
||||
@ -165,7 +165,7 @@ table.dataTable tbody td {
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th><div class="column-header">S.No </div></th>
|
||||
<th><div class="column-header">Lead Type </div></th>
|
||||
<th><div class="column-header">Opportunity Type </div></th>
|
||||
<th><div class="column-header">Issuer </div></th>
|
||||
<th><div class="column-header">Client Type </div></th>
|
||||
<th><div class="column-header">Client / Branch </div></th>
|
||||
@ -257,7 +257,7 @@ table.dataTable tbody td {
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="myCenterModalLabel">Select Lead Type</h4>
|
||||
<h4 class="modal-title" id="myCenterModalLabel">Select Opportunity Type</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -265,14 +265,14 @@ table.dataTable tbody td {
|
||||
<div class="radio-option selected" onclick="selectOption(this, 'lead_eb')">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="lead_form_type" id="lead_eb" value="1" checked>
|
||||
<label class="form-check-label" for="lead_eb">Lead EB</label>
|
||||
<label class="form-check-label" for="lead_eb">EB</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="radio-option" onclick="selectOption(this, 'lead_non_eb')">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="lead_form_type" id="lead_non_eb" value="2">
|
||||
<label class="form-check-label" for="lead_non_eb">Lead Non-EB</label>
|
||||
<label class="form-check-label" for="lead_non_eb">Non-EB</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -467,7 +467,7 @@ table.dataTable tbody td {
|
||||
lengthMenu: [[10, 20, 50, 100], [10, 20, 50, 100]],
|
||||
buttons: [
|
||||
{
|
||||
text: 'Add',
|
||||
text: '<i class="mdi mdi-plus" ></i><span class=" btn-custom"> Add </span>',
|
||||
className: 'btn app-btn-primary mr-2',
|
||||
action: function(e, dt, node, config) {
|
||||
openLeadTypeAskModal();
|
||||
@ -481,7 +481,7 @@ table.dataTable tbody td {
|
||||
{
|
||||
extend: 'csv',
|
||||
text: '<i class="mdi mdi-file-delimited " ></i><span class=" btn-custom"> CSV </span>',
|
||||
title: 'Policy-Tranction-Inception-List',
|
||||
title: 'Opportunity-List',
|
||||
className: 'app-btn-primary ',
|
||||
exportOptions: {
|
||||
columns: ':not(:last-child)'
|
||||
@ -523,7 +523,7 @@ table.dataTable tbody td {
|
||||
// let Lead_type = $('#openLeadTypeAskModal').val()
|
||||
let Lead_type = $('input[name="lead_form_type"]:checked').val();
|
||||
console.log('Lead_type ', Lead_type)
|
||||
let url = '<?=base_url('/util/getLeadNonEB/')?>' + Lead_type
|
||||
let url = '<?=base_url('/util/getLeadNonEB/')?>' + Lead_type + '/0';
|
||||
console.log('url ', url)
|
||||
window.location.href = url;
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
<a href="<?= base_url('leads/list') ?>" style="cursor: pointer;" type="button" id="btnAdd">
|
||||
<i class="mdi mdi-chevron-left" style="font-size: 43px;"></i>
|
||||
</a>
|
||||
<span class="title-text" id="page_title" style="position: relative;">Add Lead</span>
|
||||
<span class="title-text" id="page_title" style="position: relative;">Add Opportunity</span>
|
||||
</h4>
|
||||
</div>
|
||||
<form role="form" class="parsley-examples" method="post" id="leads_non_eb_form_id"
|
||||
@ -81,9 +81,9 @@
|
||||
<div class="form-row">
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="lead_type">Lead Type<span class="text-danger">*</span></label>
|
||||
<label for="lead_type">Opportunity Type<span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="lead_type" name="lead_type" required>
|
||||
<option value="">Select Lead Type</option>
|
||||
<option value="">Select Opportunity Type</option>
|
||||
<?php
|
||||
if (isset($lead_type) && count($lead_type)) {
|
||||
foreach ($lead_type as $key => $value) {
|
||||
|
||||
@ -255,7 +255,7 @@
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Email" onclick="selectType('Email', this)">✉️ Email</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Meeting" onclick="selectType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Visit" onclick="selectType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Demo" onclick="selectType('Demo', this)">🎬 Demo</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Demo" onclick="selectType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Share" onclick="selectType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Todo" onclick="selectType('Todo', this)">✓ To Do</button>
|
||||
</div>
|
||||
@ -274,8 +274,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Assigned To</label>
|
||||
<select id="act_owner" class="search-input searchable" style="width:100%">
|
||||
<label class="form-label">Assigned To <span class="text-danger">*</span></label>
|
||||
<select id="act_owner" class="search-input searchable" style="width:100%" required>
|
||||
<?php foreach($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>"><?= $user['first_name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
@ -325,7 +325,7 @@
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Email', this)">✉️ Email</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Demo', this)">🎬 Demo</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Todo', this)">✓ To Do</button>
|
||||
|
||||
@ -429,7 +429,7 @@ function resetFlatpicker(){
|
||||
});
|
||||
}
|
||||
|
||||
const activityIcons = { Call: "📞", Email: "✉️", Meeting: "📅", Visit: "🚗",Demo: "🎬", Share: "📄", Todo: "✓" };
|
||||
const activityIcons = { Call: "📞", Email: "✉️", Meeting: "📅", Visit: "🚗",Demo: "🖥️", Share: "📄", Todo: "✓" };
|
||||
const salesManagerIds = <?= json_encode($sales_manager_ids ?? []) ?>;
|
||||
|
||||
const API = '<?= base_url('sales') ?>';
|
||||
@ -480,6 +480,10 @@ function closeModal(id) {
|
||||
}
|
||||
|
||||
function openMainActivityModal() {
|
||||
document.getElementById('act_owner').value = "";
|
||||
document.getElementById('act_owner').dispatchEvent(new Event('change'));
|
||||
document.getElementById('act_lead_id').value = "";
|
||||
document.getElementById('act_lead_id').dispatchEvent(new Event('change'));
|
||||
document.getElementById('act_string_flag').value = "frommain"; // ✅ flag for main
|
||||
openModal('activityModal');
|
||||
}
|
||||
@ -786,7 +790,8 @@ function openActivityModal() {
|
||||
document.getElementById('act_owner').dispatchEvent(new Event('change'));
|
||||
document.getElementById('act_lead_id').value = lead_id;
|
||||
document.getElementById('act_lead_id').dispatchEvent(new Event('change'));
|
||||
document.getElementById('act_string_flag').value = "frompopup";
|
||||
document.getElementById('act_string_flag').value = "frompopup";
|
||||
openModal('activityModal');
|
||||
}
|
||||
|
||||
function openComp(id, assigned_to, lead_id, string_flag) {
|
||||
@ -1123,150 +1128,7 @@ document.getElementById('do_follow').addEventListener('change', function () {
|
||||
selectedFollowUpActivityType = ''; // Reset global variable
|
||||
}
|
||||
});
|
||||
const btnSaveContact = document.getElementById('btnSaveContact');
|
||||
if (btnSaveContact) {
|
||||
btnSaveContact.onclick = async (e) => {
|
||||
e.preventDefault(); // Prevent accidental form submission
|
||||
|
||||
// 1. Get and Trim Values
|
||||
const nameInput = document.getElementById('contact_name');
|
||||
const mobileInput = document.getElementById('contact_mobile');
|
||||
const leadIdInput = document.getElementById('lead_id');
|
||||
|
||||
const name = nameInput.value.trim();
|
||||
const mobile = mobileInput.value.trim();
|
||||
const lead_id = leadIdInput.value.trim();
|
||||
|
||||
// 2. Simple Client-Side Validation
|
||||
if (!name || !mobile) {
|
||||
return toastr.warning('Please enter both contact person name and mobile number.');
|
||||
}
|
||||
let payload = { lead_id: lead_id, name: name, mobile: mobile };
|
||||
// Reset previous error states
|
||||
document.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
|
||||
|
||||
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/contacts`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
// Success: Clear inputs
|
||||
nameInput.value = '';
|
||||
mobileInput.value = '';
|
||||
|
||||
// Hide "No Data" message if it exists
|
||||
const noData = document.getElementById('NoData');
|
||||
if(noData) noData.style.display = 'none';
|
||||
|
||||
// Generate HTML for the new contact
|
||||
const contactHtml = `
|
||||
<div class="contact-card d-flex justify-content-between align-items-center mb-2 p-2"
|
||||
style="background: #f8f8f8; border-radius: 8px;"
|
||||
data-id="${result.data.contact_id}">
|
||||
<div>
|
||||
<div class="fw-bold">${result.data.name}</div>
|
||||
<div class="text-muted">${result.data.mobile}</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button"
|
||||
class="btn btn-danger btn-sm btnRemoveContact"
|
||||
data-id="${result.data.contact_id}">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// <button type="button"
|
||||
// class="btn btn-secondary btn-sm btnEditContact"
|
||||
// data-id="${result.data.contact_id}">
|
||||
// Update
|
||||
// </button>
|
||||
// Append to container
|
||||
document.getElementById('savedContactsContainer').insertAdjacentHTML('beforeend', contactHtml);
|
||||
toastr.success('Contact saved successfully');
|
||||
} else {
|
||||
// Handle Errors using the 'result' we already parsed
|
||||
if (res.status === 400) {
|
||||
let errorMessages = "";
|
||||
let seenMessages = [];
|
||||
let isFirstError = true; // Fixed: Variable initialization
|
||||
|
||||
if (result.messages) {
|
||||
Object.entries(result.messages).forEach(([field, message]) => {
|
||||
// Find element by name attribute (Vanilla JS)
|
||||
let inputElement = document.querySelector(`[name="${field}"]`);
|
||||
|
||||
if (inputElement) {
|
||||
inputElement.classList.add('is-invalid');
|
||||
if (isFirstError) {
|
||||
setTimeout(() => inputElement.focus(), 100);
|
||||
isFirstError = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!seenMessages.includes(message)) {
|
||||
errorMessages += `• ${message}<br>`;
|
||||
seenMessages.push(message);
|
||||
}
|
||||
});
|
||||
toastr.error(errorMessages, 'Validation Error', { allowHtml: true });
|
||||
} else {
|
||||
toastr.warning(result.message || 'Validation failed');
|
||||
}
|
||||
} else {
|
||||
toastr.error(result.message || 'An error occurred while saving.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Fetch Error:", error);
|
||||
toastr.error('Network error or server is unreachable.');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const savedContactsContainer = document.getElementById('savedContactsContainer');
|
||||
if (savedContactsContainer) {
|
||||
savedContactsContainer.onclick = async (e) => {
|
||||
// Check if the clicked element is a Remove button
|
||||
if (e.target.classList.contains('btnRemoveContact')) {
|
||||
const btn = e.target;
|
||||
const contactId = btn.dataset.id; // Get data-id
|
||||
const card = btn.closest('.contact-card'); // Find the parent card
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/contacts/${contactId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
card.remove(); // Remove from DOM
|
||||
toastr.success('Contact removed successfully');
|
||||
|
||||
// Check if container is empty to show "No Data"
|
||||
const container = document.getElementById('savedContactsContainer');
|
||||
if (container.querySelectorAll('.contact-card').length === 0) {
|
||||
const noData = document.getElementById('NoData');
|
||||
if(noData) noData.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
toastr.error('Failed to remove contact');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toastr.error('An error occurred');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function convertDBFormatted(input) {
|
||||
|
||||
if (!input) return null;
|
||||
|
||||
@ -89,17 +89,17 @@
|
||||
<!-- <div class="stat-change text-success">↑ 12% this month</div> -->
|
||||
</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo $total_activities ?></div>
|
||||
<div class="stat-val"><?php echo $total_acts ?></div>
|
||||
<div class="stat-label">Total Activities</div>
|
||||
<!-- <div class="stat-change text-success">↑ 5% this month</div> -->
|
||||
</div>
|
||||
<div class="card-hero">
|
||||
<div class="stat-val"><?php echo ($total_activities - $completed_acts) ?></div>
|
||||
<div class="stat-val"><?php echo $total_pending_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-val"><?php echo $total_completed_acts ?></div>
|
||||
<div class="stat-label">Completed Activities</div>
|
||||
<!-- <div class="stat-change text-success">↑ 15% this month</div> -->
|
||||
</div>
|
||||
@ -113,8 +113,8 @@
|
||||
<div class="main-grid">
|
||||
<div class="card">
|
||||
<div class="table-header">
|
||||
<h3 style="font-size: 16px; font-weight: 700;">Recent Activities - All Team</h3>
|
||||
<span style="color: #718096; font-size: 12px; font-weight: 600;">6 activities</span>
|
||||
<h3 style="font-size: 16px; font-weight: 700;">Pending Activities - All Team</h3>
|
||||
<span style="color: #718096; font-size: 12px; font-weight: 600;"><?= count($pending_acts) . ' ' . (count($pending_acts) == 1 ? 'activity' : 'activities') ?></span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
@ -126,21 +126,21 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($recent_acts)): ?>
|
||||
<?php foreach ($recent_acts as $a): ?>
|
||||
<?php if (!empty($pending_acts)): ?>
|
||||
<?php foreach ($pending_acts as $a): ?>
|
||||
<?php
|
||||
$activityIcons = [
|
||||
'Call' => '📞',
|
||||
'Email' => '✉️',
|
||||
'Meeting' => '📅',
|
||||
'Visit' => '🚗',
|
||||
'Demo' => '🎬',
|
||||
'Demo' => '🖥️',
|
||||
'Share' => '📄',
|
||||
'Todo' => '✓'
|
||||
];
|
||||
$icon = $activityIcons[$a['activity_type']] ?? '📌';
|
||||
$statusClass = strtolower(str_replace(' ', '-', $a['status']));
|
||||
$formattedDate = date('d M Y', strtotime($a['scheduled_date']));
|
||||
$formattedscheduledDate = date('M d, Y, h:i A', strtotime($a['scheduled_date']));
|
||||
?>
|
||||
<tr>
|
||||
<td><strong><?= esc($a['company_name']) ?></strong></td>
|
||||
@ -151,8 +151,11 @@
|
||||
<div style="font-size: 12px; color: #718096;">
|
||||
<?= esc($a['assigned_to_name'] ?? 'ID: ' . $a['assigned_to']) ?>
|
||||
</div>
|
||||
<!-- <div style="font-size: 12px; color: #718096;">
|
||||
<?= esc($a['notes'] ?? 'ID: ' . $a['assigned_to']) ?>
|
||||
</div> -->
|
||||
</td>
|
||||
<td style="color: #4a5568; font-weight: 500;"><?= $formattedDate ?></td>
|
||||
<td style="color: #4a5568; font-weight: 500;"><?= $formattedscheduledDate ?></td>
|
||||
<td><span class="status-pill status-<?= $statusClass ?>"><?= ucfirst(esc($a['status'])) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -213,7 +216,7 @@
|
||||
'Email' => ['color' => '#48bb78', 'icon' => '✉️'],
|
||||
'Meeting' => ['color' => '#4299e1', 'icon' => '📅'],
|
||||
'Visit' => ['color' => '#ecc94b', 'icon' => '🚗'],
|
||||
'Demo' => ['color' => '#9f7aea', 'icon' => '🎬'],
|
||||
'Demo' => ['color' => '#9f7aea', 'icon' => '🖥️'],
|
||||
'Share' => ['color' => '#ed8936', 'icon' => '📄'],
|
||||
'Todo' => ['color' => '#718096', 'icon' => '✓'],
|
||||
];
|
||||
@ -228,7 +231,7 @@
|
||||
?>
|
||||
<div class="breakdown-item">
|
||||
<span>
|
||||
<span class="dot" style="background: <?= $color ?>;"></span>
|
||||
<span style="background: <?= $color ?>;"></span>
|
||||
<?= $icon ?> <?= esc($type) ?>
|
||||
</span>
|
||||
<strong><?= $item['percentage'] ?>%</strong>
|
||||
@ -244,7 +247,13 @@
|
||||
<div class="card">
|
||||
<div class="table-header">
|
||||
<h3 style="font-size: 16px; font-weight: 700;">All Leads Overview</h3>
|
||||
<span style="color: #718096; font-size: 12px; font-weight: 600;">Click a lead to see details</span>
|
||||
<!-- <span style="color: #718096; font-size: 12px; font-weight: 600;">Click a lead to see details</span> -->
|
||||
<a href="<?= base_url('sales') ?>"
|
||||
style="color: #718096; font-size: 12px; font-weight: 600; text-decoration: none; padding: 4px 8px; transition: color 0.2s ease; display: inline-block; cursor: pointer;"
|
||||
onmouseover="this.style.color='#ff6b35';"
|
||||
onmouseout="this.style.color='#718096';">
|
||||
Click a lead to see details
|
||||
</a>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
@ -48,6 +48,23 @@
|
||||
.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); }
|
||||
|
||||
/* Fix for the grid layout on smaller screens */
|
||||
@media (max-width: 992px) {
|
||||
.dashboard-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* Fix for inputs inside modal rows */
|
||||
input, select, textarea {
|
||||
width: 100%; /* Change from 400px to 100% */
|
||||
box-sizing: border-box; /* Ensures padding doesn't add to width */
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div class="my-dash">
|
||||
@ -99,13 +116,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="list-card">
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:15px; align-items: center;">
|
||||
<h3 style="font-size:16px; font-weight: 700; margin: 0;">My Upcoming Activities</h3>
|
||||
<span style="color:#ff6b35; font-size:12px; font-weight: 600;"><?= $acts['pending'] ?> pending</span>
|
||||
</div>
|
||||
<?php foreach($upcoming as $u): ?>
|
||||
<!-- <?php foreach($upcoming as $u): ?>
|
||||
<div class="list-item">
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:14px; color: #333;"><?= $u['company_name'] ?></div>
|
||||
@ -113,13 +132,41 @@
|
||||
</div>
|
||||
<button class="badge-done" onclick="openComp(<?= $u['activity_id'] ?>,<?= $u['lead_id'] ?>)">✓ Mark as completed</button>
|
||||
</div>
|
||||
<?php endforeach; ?> -->
|
||||
<?php foreach($upcoming as $u):
|
||||
$activityIcons = [ 'Call' => '📞', 'Email' => '✉️', 'Meeting' => '📅', 'Visit' => '🚗', 'Demo' => '🖥️', 'Share' => '📄', 'Todo' => '✓' ];
|
||||
$icon = $activityIcons[$u['activity_type']] ?? '📌';
|
||||
$formattedscheduledDate = date('M d, Y, h:i A', strtotime($u['scheduled_date']));
|
||||
?>
|
||||
<div class="list-item" style="display: flex; align-items: center; gap: 15px;">
|
||||
<div class="lead-initial" style="flex-shrink: 0;">
|
||||
<?= $icon ?>
|
||||
</div>
|
||||
|
||||
<div style="flex-grow: 1; display: flex; flex-direction: column; min-width: 0;">
|
||||
<div style="font-weight: 700; font-size: 14px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<?= esc($u['company_name']) ?>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #999; margin-top: 2px; white-space: nowrap;">
|
||||
<span style="color: #ff6b35; font-weight: 600;"><?= strtoupper($u['activity_type'] ?? 'EMAIL') ?></span>
|
||||
- <?= $formattedscheduledDate ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="flex-shrink: 0;">
|
||||
<button class="badge-done" onclick="openComp(<?= $u['activity_id'] ?>, <?= $u['lead_id'] ?>)">
|
||||
✓ Mark as completed
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="list-card">
|
||||
<h3 style="font-size:16px; font-weight: 700; margin-bottom:20px;">My Recent Leads</h3>
|
||||
<?php foreach($recent_leads as $rl): ?>
|
||||
<div class="list-item">
|
||||
<!-- <div class="list-item">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<div class="lead-initial"><?= substr($rl['company_name'], 0, 1) ?></div>
|
||||
<div>
|
||||
@ -128,6 +175,35 @@
|
||||
</div>
|
||||
</div>
|
||||
<span class="status-pill" style="background: #e3f2fd; color: #1976d2;"><?= $rl['status'] ?></span>
|
||||
</div> -->
|
||||
<div class="list-item" style="display: flex; align-items: center; gap: 15px; padding: 12px 0; border-bottom: 1px solid #f9f9f9;">
|
||||
|
||||
<div style="flex-shrink: 0;">
|
||||
<div class="avatar-title"
|
||||
style="background-color: #f1f3fa; width: 42px; height: 42px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #1976d2; font-weight: 700; font-size: 16px;">
|
||||
<?php
|
||||
$firstLetter = !empty($rl['company_name']) ? substr($rl['company_name'], 0, 1) : '?';
|
||||
echo strtoupper(esc($firstLetter));
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="flex-grow: 1; min-width: 0;">
|
||||
<div style="font-weight: 600; font-size: 14px; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<?= esc($rl['company_name']) ?>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #999; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<?= esc($rl['email']) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="flex-shrink: 0;">
|
||||
<span class="status-pill"
|
||||
style="background: #e3f2fd; color: #1976d2; padding: 4px 10px; border-radius: 12px; font-size: 10px; font-weight: 600; white-space: nowrap;">
|
||||
<?= esc($rl['status']) ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
@ -185,7 +185,8 @@
|
||||
</div>
|
||||
<div class="col-md-12" style="width: 94%;">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="text" class="form-control" name="phone" style="width: 100%;" placeholder="Enter the Phone Number" oninput="this.value = this.value.replace(/[^\d\s+]/g, '')">
|
||||
<!-- <input type="text" class="form-control" name="phone" style="width: 100%;" placeholder="Enter the Phone Number" oninput="this.value = this.value.replace(/[^\d\s+]/g, '')"> -->
|
||||
<input type="text" class="form-control" name="phone" style="width: 100%;" placeholder="Enter the Phone Number" maxlength="10" oninput="this.value = this.value.replace(/[^0-9]/g, '').substring(0, 10);">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row mb-1">
|
||||
@ -283,7 +284,7 @@
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Email" onclick="selectType('Email', this)">✉️ Email</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Meeting" onclick="selectType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Visit" onclick="selectType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Demo" onclick="selectType('Demo', this)">🎬 Demo</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Demo" onclick="selectType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Share" onclick="selectType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Todo" onclick="selectType('Todo', this)">✓ To Do</button>
|
||||
|
||||
@ -352,7 +353,7 @@
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Email', this)">✉️ Email</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Demo', this)">🎬 Demo</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Todo', this)">✓ To Do</button>
|
||||
|
||||
@ -502,7 +503,8 @@
|
||||
</div>
|
||||
<div class="col-md-12" style="width: 94%;">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="text" class="form-control" name="phone" style="width: 100%;" placeholder="Enter the Phone Number" oninput="this.value = this.value.replace(/[^\d\s+]/g, '')">
|
||||
<!-- <input type="text" class="form-control" name="phone" style="width: 100%;" placeholder="Enter the Phone Number" oninput="this.value = this.value.replace(/[^\d\s+]/g, '')"> -->
|
||||
<input type="text" class="form-control" name="phone" style="width: 100%;" placeholder="Enter the Phone Number" maxlength="10" oninput="this.value = this.value.replace(/[^0-9]/g, '').substring(0, 10);">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mb-1">
|
||||
@ -934,7 +936,7 @@ function renderCard(opps) {
|
||||
}
|
||||
function renderTimeline(acts) {
|
||||
const cont = document.getElementById('timelineContainer');
|
||||
const activityIcons = { Call: "📞", Email: "✉️", Meeting: "📅", Visit: "🚗",Demo: "🎬", Share: "📄", Todo: "✓" };
|
||||
const activityIcons = { Call: "📞", Email: "✉️", Meeting: "📅", Visit: "🚗",Demo: "🖥️", Share: "📄", Todo: "✓" };
|
||||
|
||||
if (acts.length === 0) {
|
||||
cont.classList.add('no-line');
|
||||
@ -1044,7 +1046,8 @@ document.getElementById('addLeadForm').onsubmit = async (e) => {
|
||||
|
||||
let companyRegex = /^[A-Za-z\s]+$/;
|
||||
let emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
let phoneRegex = /^\+?[0-9\s]{10,20}$/; // Phone regex (+, numbers, spaces allowed, 10-20 length)
|
||||
// let phoneRegex = /^\+?[0-9\s]{0,10}$/; // Phone regex (+, numbers, spaces allowed, 10-20 length)
|
||||
let phoneRegex = /^\d{10}$/;
|
||||
|
||||
// 1. HELPER FUNCTION: Shows Toastr and focuses the specific field
|
||||
function showError(message, fieldName) {
|
||||
@ -1081,7 +1084,8 @@ document.getElementById('addLeadForm').onsubmit = async (e) => {
|
||||
}
|
||||
|
||||
if (phone && !phoneRegex.test(phone)) {
|
||||
return showError("Phone number can contain only +, numbers and spaces (10–20 digits).", 'phone');
|
||||
// return showError("Phone number can contain only +, numbers and spaces (10–20 digits).", 'phone');
|
||||
return showError("Phone number can contain only numbers (10 digits)", 'phone');
|
||||
}
|
||||
|
||||
if (!data.status) {
|
||||
@ -1170,7 +1174,8 @@ document.getElementById('editLeadForm').onsubmit = async (e) => {
|
||||
|
||||
let companyRegex = /^[A-Za-z\s]+$/;
|
||||
let emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
let phoneRegex = /^\+?[0-9\s]{10,20}$/;
|
||||
// let phoneRegex = /^\+?[0-9\s]{10,20}$/;
|
||||
let phoneRegex = /^\d{10}$/;
|
||||
let gstRegex = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][0-9A-Z]Z[0-9A-Z]$/;
|
||||
|
||||
// 1. HELPER FUNCTION: Shows Toastr and focuses the specific field
|
||||
@ -1208,7 +1213,8 @@ document.getElementById('editLeadForm').onsubmit = async (e) => {
|
||||
}
|
||||
|
||||
if (phone && !phoneRegex.test(phone)) {
|
||||
return showError("Phone number can contain only +, numbers and spaces (10–20 digits).", 'phone');
|
||||
// return showError("Phone number can contain only +, numbers and spaces (10–20 digits).", 'phone');
|
||||
return showError("Phone number can contain only numbers (10 digits)", 'phone');
|
||||
}
|
||||
|
||||
if (gst && !gstRegex.test(gst)) {
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "gdrive-demo-394007",
|
||||
"private_key_id": "5b1d856b0c5b13e52b5210d381ce7ae02204f666",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC9tqkwe8XEuvDJ\ngDAKHn7FzFgkmor9sEZPkjofVJ1dK0RpD1mMVw38BzzMsEo8Y8aojNKj9FcgJPI+\nMiSwvoHDVGxPuyz2Q7o8BS7WdM83Sn69CBGn+0s+YjsyQ5pQBeu1YZ3erjckyA1M\nX0a0qXSFRm7dN0pwDIk0SP9/pl5iAKEtakuXn/Q+lSIpHz6OUgQy8bCjn91poMtF\nhL1YR/7k8ZttjQiFSCTzQ6e3IJmVbqY9UZ2hc4zVicLVSLM2x17M1CCVrsXoceOj\nQoYEeCqP2qJjGA29CB66xpXetuOFcll/ZHiNBhSekOBbJIKFG8WfTKaFn4hu2HX6\nzbJLse5FAgMBAAECggEAXK8qxWcS3eQ+0xLvZWI0qUoGHgvqr7o4/5L/FmNuZiBH\nUdSP+UJmsKSQjafq/Mn6OkpidntfPXMPbldtGXRZTSanq+RUORQpnj0h/uAehHK+\nrHeOuLTKs/Wl2g6xCzt5RqokSLBwfGXIKXG6x3SqWppoe2cR1OArAAJR4PlUzyeM\nP0HkZcVMDrXkshpbi/7yk1Yol5CTJjUrXT4cH2eFSih+eu5UxI/uEdxu86XnaB+V\nBDS94nSQffaMem3YLRSQpPWMHJts3NM2eoxpVy3NqbyHH0Jzr47T5+pdnk5AZX8v\nMu7L0FYgUmGli8W9/jV43lUi9z147EpNC1ugySICGQKBgQDeT3i9mBO1HQB+3y05\n4ao9YKWtR7RHTIiMBkekRs58xxWIq75+bsJtLMy0GsHOJHbpdfDU2AtcasYXVm/L\nc960AR7Qm0mdhQi5CG7XfFTvkc+RhsFoCAYOnbdInr1D+s7NsyOgdYvzh9HYIiJ/\nm8MzeGQia/4tlO+UNq7UK0sfowKBgQDadpe0OCynXEiziSY/aBT9mrjplXJSJUeR\nX5/pUrBV++mYt+LJU0Q+4op0Qf+PUJwp72O1v3T9h4ox2BcrYUMI17dJZ5HG46BG\n6Sjh+mZzLCTN9L6AVgRzK/5CUpsL+oPpClzGQK+1uJ+YKZD086YBuQi2JiG6uBxk\n7VLzATZ49wKBgQC3ZHgGb95SGoq+Hv4AMdluqLwEJpLh/pDmcofHTWIqLVHmXUfY\npSZfSgXUzf3zQMGX9mOmMlOs+ahQuE2hWQTvGb2B+ZjRCV4Yxowp17d5qp/BPZlv\naK8Wf6Ujk1AvNEhGCPHq/Q1m6TSDSCWNf8GYREjW3J/immrJqhKvlMd0YQKBgQCM\nf2CpQsdVCwCmljnG5YU6ZFsvvjE7q0YPtFP/lnJZmh1tXjW4DJkDaGZqxlc5MDp+\nrbqOlIcE1jqGO9cKyw51jWYPC1CxfIsDj8f/LS7eOzGgUxqBJtDN0SlANigI2CAl\nq8hmqAtY71eUYIcdQeUtjnaPzo46q1V3gzmplsoVmQKBgDCzaBbFY1bKW+xPPC98\nutYS7cGYtTq241zafSXx/qmpZB2QwFsR3rmZ9msCEBb3TY4Ew+hUi7+SP3ycSU1s\nkwIO/9DaZhFBRA9jbwbu4zdB2Niamfo79epqJ8vhJ6TA2d5xRSGbHWkGymWV/e0p\na74mh5hQ0lqI4MSeArFrJwwF\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "gsheet@gdrive-demo-394007.iam.gserviceaccount.com",
|
||||
"client_id": "108808196910972902964",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/gsheet%40gdrive-demo-394007.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "nhance-app-4fedb",
|
||||
"private_key_id": "e764472bb4a0c9226d172f7aebdd2eb785c5fca8",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCzyKk6yQqLZHnL\n8+qRwMHtIhV3kR6bYuCol7LTU/aRyTZpFafewJ/6y5bMvvHaRSaohuuTjDhp5H+w\nf7DAiWO06C/YdgZ/tv3dPM8FnkOEnwxUXVgJskK+srJ4g6zsYkT+0DNI0qMJRW5m\n0jv+HKPJ0axCAPcUZUVtraV/HFHF7pJp4oWMFD5X7Jhv7kbW3TetJofSN3SvRvr8\nyfCiVGzqgF0Gx0Tgd25GTMOuRXTGoTlQeDHFMm19g8YY1de0Ol9WqFey6E8e6a/h\nw8KHGdywgsI+q5yF0EIUdxKc6U9Bea25k0YnKHSCsJjJTVlKW+xlxR6wA1yuBb66\n2dAhEzldAgMBAAECggEACEssKpb5dadVPdt+PJ2r58CueD1JfRL8i/p98hhjY9bo\niJEXSFDfI6aYFp22ZYKfWNK3wMimr5SuPIB5F6C4gn3b+y95ERPQtg/AkyDgB39M\nBjZNW4EnabAWFWlc2FJknjnd+JcmLnhoy+h/gpnKoOkbYISDFKQWJfJtRvLE/1on\nhKvnhEkO++93HZp6KPYA21BQIWt6q6gYjjjwxQIHE0Vg30bZC8ciNVaVlmOnpoKW\nlsQ3+TL+b6vDsSxez4hSqWlua7wWL6JgKvyGw6FGiDLGjvTEvVQ0cvfykCDmO5td\nWvVihTMOMXEMx+iVAUOXJLrSPOFGXxSHL9jilqCn0QKBgQDu4P64sE5T15rA+83B\neRMyQUfLru/oxyQ7pAbgRk4nwQj1mXcdD3/RpHF6jg0rt0msRjkzkqyWiJN0qsZ9\nlQY7h0I8pu8dxFhbd7LLcPaNV+OpNAokG06dBK8qhuMcZBlRfvJ3ye5L/Dk0Z7A4\nPw6/OxySns+kzfxtPdpJTNeqFQKBgQDAq2BXm+MsjqjZm2Z1LFMgZcjRLUu4ylMC\n9W3jqd35dBypcafAJ3TqhPLsx+HyWuC+GLMWohx3XGU7oZabGg1zuWAS8A4hnogZ\nk47OT3OYHDaT65HFmSAGJy1UFVvESUdiVzPqO3Xn9dYvRIw6By2b09w+kD1oxmE0\nvAh5UFAMKQKBgHg06plfxxqzzWE9lS290qYgaZOaxYla5OXKRdeIKX4hynNktab/\nDLAfUyd74i7UfhNeBxznu0fJFILKCTZazpcYGoHQ81UEX/4vPt7XSoqX5q1XzZ6b\nyzSCje8Vj6XSzVbQTg1vpSXBl7vCTdAQE0ix40/48L6bFWKXlIf8Ti59AoGAd8UR\nKuT5H6W/SSbVVlmrgyRC5eWmgMUlPV2cZj4egwevGZZRlZ3xjCgBazgGOUZNF177\nVUKJN5n0RFF68ggL/LhsBNm9ryCBsoSL7axuS0yekO2LvK4nvit0fiSY2zhCa9uR\npjY4YW3tK40NLrVvoMLe0vWPdyZ7HvEUw3UtjaECgYAMoonkNACD7EyWesAQzjpD\ntAL5ByXtFkMqMMr8ELLoDU2D5+qzb6gu0+GuYzGvxtYQHz2L+cP7yonn8IRBrAO1\njZCi2Mn1qOYecSGBnv6rotcagMALXwExkymjjVxPLiOae7A01sxSFDpqtRBww2JA\ngFsHNMpkQ1KmZu7LW0q1fg==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "only-drive-access@nhance-app-4fedb.iam.gserviceaccount.com",
|
||||
"client_id": "116214376249781337850",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/only-drive-access%40nhance-app-4fedb.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
13
nhance-ee8d1-e3c5269b1ec7.json
Normal file
13
nhance-ee8d1-e3c5269b1ec7.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "nhance-ee8d1",
|
||||
"private_key_id": "e3c5269b1ec7d22e7a297f5273edf3eda6010c0a",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4+7yRGBAow7GR\nrcBSwXVkBd7U001YC8nZ6GmbfhZSktn4pd4C7r/VGmJMcHdo4CVkt+omY9hEWy3Y\n2k7xvwTBJOxo+A4GENduzUpRucVD5UNs4CKajVmI97FV+2wgeLYR9lLswrSXkQvo\nCRDi/w0EwQ7ZdfPyYfuUUBSdAytiwtGHEJaHBQ4qanNvstFu6FJcCkaq+Sbn6Ool\nHN7lgS/V5NhuBakNOm8EBBaN8Xp13afjHvyFEHalnY8xpNNXNVlBljYIPzYFxXFN\nFL/Iz7XX8dBD0tVwfQGDpD5OCPh8FeIg39dgbRxU3Q+Awv3CX9eoEW1j3bPYo7Lw\nmh8K3fohAgMBAAECggEAEQXPCfxQmSrwOPg0zEjHDTyb3DKGGdw0p772YChy7gfB\nudrfR2s1SWtWo8y7tSTfVaZPcvEmnzYgZ3LB56EAIBs0pP1viO/20aVgBlpI99Q6\ntEobiEs9DViOpkHVs71EzMb1Emcugde6sUOLg5NPkHu8BDErERSJBFJ3bliy0heN\nwAwYKXCB3ro3ryUE5+pRS6ibt8vLEBbk3T3bEW8A1wfWnUo0V+Wf5KXsCO8BvD8Z\nE/i0JgpAkZksWs+keBSLbdWBONNV1GrbxezoZXA8aPPlcaD0bZdLDlszXguq8DGO\nbjr0SI7Hx78/jAQhiFkdNMkLvZMIoU7VVnQSibsBKwKBgQD04PMi6CO6kpKG/ZDw\nR6tuAU8DkUgSjvOquUeGkguyfhK/G5fg3w8yCpLAPoh8ZEGL4zZy+ZoAsHWLv44O\nmXjTOKut5o1puswM8vPobDAinddCWs6U6VWKkkRw27AuyKcnLCpvnRBg/Iw+lSTQ\nzG0KCd8Zn2+a0Cjo9hlF6F2pAwKBgQDBYmvV+cADTRjhostukDK71aKsRhMu1OH4\nRK2uX3f9rde1aSnIcp0PDzn24V7I9skT6zhOCPTF6SEFH35eO3z7kOA26hokOMb2\nz7Bh+EDnx5zQmWwcj0yNwbCeTl7gKWz2o3AuMc8zK6I2+buYJJnjoazUyqQtKhqz\nnPs3woY9CwKBgDHjwqVR9jWEtyWZc3YApAR9b8OiTbS6OxqFNPVNu+RZmygkTwUZ\nbNcdIFjaZKQzKMd/OxChmaaaTNhz5lVDH0KpQRDk79qim//nX5nysLvcvIZgScY8\n45ifxCHaIELnzmZEsUCcF0IrMcduS1nezDhHWpS1zt8TmcIcoXmEpdBRAoGAEtKs\nomAz415evJ+m43Ufqw7JTbFobpeEAzFUInPibwu7wkmhKoSVawDVaIVZP4Bd5BVy\nHo5anOTrNN9y4mMx8B6S5GV28+2e2CkxBuguESFpzxgP4NvF3MpskYwZSgJeO8d+\nxNBOVbG6kVVPgCiX3gM/mlq7DjZZ8P+nqC7D+C8CgYAMeN/ByvGaYSN5bVVntOcT\noDiMWfBBToc9irhNSZsCwghXmNugPKguDbRTi7n/Iqf/HljwUx0HBtXkIgZvDDiW\nQrGSCozd9g2WFx/2OSJv4GU+eUM1GFr0mLFDaAUNzvjwHTkG+2STof8VowaGRx68\n7Z50l5hXKv3i0n5ykhPWUg==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "firebase-adminsdk-mcdfe@nhance-ee8d1.iam.gserviceaccount.com",
|
||||
"client_id": "105441165052433248287",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-mcdfe%40nhance-ee8d1.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user