| Server IP : 72.60.21.38 / Your IP : 216.73.216.25 Web Server : LiteSpeed System : Linux uk-fast-web1372.main-hosting.eu 4.18.0-553.121.1.lve.el8.x86_64 #1 SMP Thu Apr 30 16:40:41 UTC 2026 x86_64 User : u390967363 ( 390967363) PHP Version : 8.2.30 Disable Function : system, exec, shell_exec, passthru, mysql_list_dbs, ini_alter, dl, symlink, link, chgrp, leak, popen, apache_child_terminate, virtual, mb_send_mail MySQL : OFF | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /home/u390967363/domains/aibenproperties.com/public_html/app/ |
Upload File : |
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/functions.php';
$role = $_SESSION['user_role'] ?? 'guest';
$allowed = ['super_admin','admin','customer_rep','contact_rep'];
if (!in_array($role, $allowed)) {
header("Location: dashboard.php");
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$companyId = getCurrentCompanyId();
$af = null;
if (tableHasColumn('support_tickets', 'assignee_id')) { $af = 'assignee_id'; }
elseif (tableHasColumn('support_tickets', 'assigned_to')) { $af = 'assigned_to'; }
if (isset($_POST['action']) && $_POST['action'] === 'update_ticket') {
$id = isset($_POST['id']) && ctype_digit($_POST['id']) ? (int)$_POST['id'] : 0;
$status = $_POST['status'] ?? '';
$assignToMe = isset($_POST['assign_me']) && $_POST['assign_me'] === '1';
$priority = $_POST['priority'] ?? '';
$escalate = isset($_POST['escalate']) && $_POST['escalate'] === '1';
if ($id) {
try {
$set = [];
$params = [];
if (in_array($status, ['open','pending','resolved','closed'])) { $set[] = "status = ?"; $params[] = $status; }
if ($assignToMe && $af) { $set[] = "$af = ?"; $params[] = (int)($_SESSION['user_id'] ?? 0); }
if (in_array($priority, ['low','medium','high','urgent'])) { $set[] = "priority = ?"; $params[] = $priority; }
if ($escalate) { $set[] = "priority = 'urgent'"; }
if ($set) {
$sql = "UPDATE support_tickets SET " . implode(", ", $set) . " WHERE id = ?";
$params[] = $id;
if (tableHasColumn('support_tickets', 'company_id') && $companyId) { $sql .= " AND company_id = ?"; $params[] = $companyId; }
$st = $pdo->prepare($sql);
$st->execute($params);
header("Location: customer-rep-dashboard.php?notice=Ticket%20updated&type=success");
exit;
}
} catch (Exception $e) {
header("Location: customer-rep-dashboard.php?notice=Update%20failed&type=danger");
exit;
}
}
}
if (isset($_POST['action']) && $_POST['action'] === 'create_ticket') {
$subject = trim($_POST['subject'] ?? '');
$message = trim($_POST['message'] ?? '');
$priority = $_POST['priority'] ?? 'medium';
$user_id = $_SESSION['user_id'];
if ($subject !== '' && $message !== '') {
try {
$valid = ['low','medium','high','urgent'];
if (!in_array($priority, $valid)) $priority = 'medium';
$pdo->beginTransaction();
if ($companyId) {
$stmt = $pdo->prepare("INSERT INTO support_tickets (company_id, user_id, subject, message, priority, status) VALUES (?, ?, ?, ?, ?, 'open')");
$stmt->execute([$companyId, $user_id, $subject, $message, $priority]);
} else {
$stmt = $pdo->prepare("INSERT INTO support_tickets (user_id, subject, message, priority, status) VALUES (?, ?, ?, ?, 'open')");
$stmt->execute([$user_id, $subject, $message, $priority]);
}
$ticket_id = $pdo->lastInsertId();
$attachmentPath = null;
if (isset($_FILES['attachment'])) {
$attachmentPath = handleFileUpload('attachment', 'uploads/tickets/');
}
$msgStmt = $pdo->prepare("INSERT INTO ticket_messages (ticket_id, sender_id, message, attachment_path) VALUES (?, ?, ?, ?)");
$msgStmt->execute([$ticket_id, $user_id, $message, $attachmentPath]);
$pdo->commit();
header("Location: customer-rep-dashboard.php?notice=Ticket%20created&type=success");
exit;
} catch (Exception $e) {
try { $pdo->rollBack(); } catch (Exception $e2) {}
header("Location: customer-rep-dashboard.php?notice=Failed%20to%20create%20ticket&type=danger");
exit;
}
} else {
header("Location: customer-rep-dashboard.php?notice=Subject%20and%20message%20required&type=danger");
exit;
}
}
}
include 'includes/header.php';
if (!function_exists('dashboardOccurrenceReminder')) {
function dashboardOccurrenceReminder($dateValue) {
if (!$dateValue) {
return null;
}
$sourceDate = strtotime((string)$dateValue);
if (!$sourceDate) {
return null;
}
$todayTs = strtotime(date('Y-m-d'));
$occurrenceTs = strtotime(date('Y', $todayTs) . '-' . date('m-d', $sourceDate));
if (!$occurrenceTs) {
return null;
}
if ($occurrenceTs < $todayTs) {
$occurrenceTs = strtotime('+1 year', $occurrenceTs);
}
$daysUntil = (int)floor(($occurrenceTs - $todayTs) / 86400);
return [
'days_until' => $daysUntil,
'occurs_on' => date('M d', $occurrenceTs),
'label' => $daysUntil === 0 ? 'Today' : 'In ' . $daysUntil . ' day' . ($daysUntil === 1 ? '' : 's'),
'timestamp' => $occurrenceTs
];
}
}
if (!function_exists('dashboardDisplayDate')) {
function dashboardDisplayDate($dateValue) {
if (!$dateValue) {
return 'Not available';
}
$timestamp = strtotime((string)$dateValue);
if (!$timestamp) {
return 'Not available';
}
return date('F j, Y', $timestamp);
}
}
$companyId = getCurrentCompanyId();
$status = $_GET['status'] ?? '';
$assignedTo = $_GET['assigned_to'] ?? '';
$priorityFilter = $_GET['priority'] ?? '';
$overSla = isset($_GET['over_sla']) && $_GET['over_sla'] === '1';
$search = strtolower(trim($_GET['q'] ?? ''));
$params = [];
$where = [];
if ($status !== '' && in_array($status, ['open','pending','resolved','closed'])) { $where[] = "t.status = ?"; $params[] = $status; }
$assigneeField = null;
if (tableHasColumn('support_tickets', 'assignee_id')) { $assigneeField = 'assignee_id'; }
elseif (tableHasColumn('support_tickets', 'assigned_to')) { $assigneeField = 'assigned_to'; }
if ($assignedTo === 'me' && $assigneeField) { $where[] = "t.$assigneeField = ?"; $params[] = (int)($_SESSION['user_id'] ?? 0); }
if ($priorityFilter !== '' && in_array($priorityFilter, ['low','medium','high','urgent'])) { $where[] = "t.priority = ?"; $params[] = $priorityFilter; }
if (tableHasColumn('support_tickets', 'company_id') && $companyId) { $where[] = "t.company_id = ?"; $params[] = $companyId; }
$slaCond = "";
if ($overSla) { $slaCond = " AND TIMESTAMPDIFF(HOUR, t.created_at, NOW()) > 24"; }
$whereSql = $where ? ("WHERE " . implode(" AND ", $where)) : "";
$assigneeJoin = $assigneeField ? " LEFT JOIN users a ON t.$assigneeField = a.id " : "";
$sql = "SELECT t.*, u.name as client_name" . ($assigneeField ? ", a.name as assignee_name" : "") . " FROM support_tickets t LEFT JOIN users u ON t.user_id = u.id" . $assigneeJoin . " $whereSql" . $slaCond . " ORDER BY t.created_at DESC LIMIT 200";
$st = $pdo->prepare($sql);
$st->execute($params);
$tickets = $st->fetchAll();
$counts = ['open' => 0, 'pending' => 0, 'resolved' => 0];
$avg_res_hours = 0; $res_count = 0; $sla_breaches = 0;
$thresholds = ['urgent' => 24, 'high' => 48, 'medium' => 72, 'low' => 120];
$hasDue = tableHasColumn('support_tickets', 'due_date');
$assigneeIdForAttr = $assigneeField ?: '';
foreach ($tickets as $t) {
$s = strtolower($t['status'] ?? '');
if ($s === 'open') $counts['open']++;
elseif ($s === 'pending') $counts['pending']++;
elseif ($s === 'resolved') $counts['resolved']++;
if ($s === 'resolved') {
$start = isset($t['created_at']) ? strtotime($t['created_at']) : null;
$end = isset($t['updated_at']) ? strtotime($t['updated_at']) : null;
if ($start && $end && $end >= $start) { $avg_res_hours += ($end - $start) / 3600; $res_count++; }
} else {
$prio = strtolower($t['priority'] ?? 'medium');
$thr = $thresholds[$prio] ?? 72;
$start = isset($t['created_at']) ? strtotime($t['created_at']) : null;
if ($start) { $hours = (time() - $start) / 3600; if ($hours > $thr) $sla_breaches++; }
}
}
$avg_res_hours = $res_count > 0 ? round($avg_res_hours / $res_count, 1) : 0;
try {
if (tableHasColumn('support_tickets', 'company_id') && $companyId) {
$c = $pdo->prepare("SELECT status, COUNT(*) c FROM support_tickets WHERE company_id = ? GROUP BY status");
$c->execute([$companyId]);
} else {
$c = $pdo->query("SELECT status, COUNT(*) c FROM support_tickets GROUP BY status");
}
foreach ($c as $row) { $k = strtolower($row['status']); if (isset($counts[$k])) $counts[$k] = (int)$row['c']; }
} catch (Exception $e) {}
?>
<?php
$resolved_today = 0; $new_today = 0; $my_assigned = 0;
try {
$paramsRT = []; $paramsNT = []; $paramsMA = [];
$condComp = "";
if (tableHasColumn('support_tickets','company_id') && $companyId) { $condComp = " AND company_id = ?"; $paramsRT[] = $companyId; $paramsNT[] = $companyId; }
$stc = $pdo->prepare("SELECT COUNT(*) FROM support_tickets WHERE status = 'resolved' AND DATE(updated_at) = CURDATE()" . $condComp);
$stc->execute($paramsRT); $resolved_today = (int)$stc->fetchColumn();
$stn = $pdo->prepare("SELECT COUNT(*) FROM support_tickets WHERE DATE(created_at) = CURDATE()" . $condComp);
$stn->execute($paramsNT); $new_today = (int)$stn->fetchColumn();
if ($assigneeField) {
$sqlMA = "SELECT COUNT(*) FROM support_tickets WHERE $assigneeField = ? AND status IN ('open','pending')";
$paramsMA[] = (int)($_SESSION['user_id'] ?? 0);
if ($companyId && tableHasColumn('support_tickets','company_id')) { $sqlMA .= " AND company_id = ?"; $paramsMA[] = $companyId; }
$stm = $pdo->prepare($sqlMA); $stm->execute($paramsMA); $my_assigned = (int)$stm->fetchColumn();
}
} catch (Exception $e) {}
$leadTable = 'referrals';
try {
$leadTableExists = (int)$pdo->query("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'leads'")->fetchColumn() > 0;
if ($leadTableExists) { $leadTable = 'leads'; }
} catch (Exception $e) {}
$new_leads_today = 0; $followups_today = 0; $birthdays_upcoming = 0; $anniversaries_upcoming = 0; $calls_made_today = 0; $deals_closed_today = 0;
try {
$sqlNewLeads = "SELECT COUNT(*) FROM {$leadTable} WHERE DATE(created_at) = CURDATE()";
$paramsNewLeads = [];
if (tableHasColumn($leadTable,'company_id') && $companyId) { $sqlNewLeads .= " AND company_id = ?"; $paramsNewLeads[] = $companyId; }
$st = $pdo->prepare($sqlNewLeads);
$st->execute($paramsNewLeads);
$new_leads_today = (int)$st->fetchColumn();
} catch (Exception $e) {}
try {
if (tableHasColumn('contact_logs','next_contact_at')) {
$sqlFollow = "SELECT COUNT(*) FROM contact_logs WHERE DATE(next_contact_at) = CURDATE()";
$paramsFollow = [];
if (tableHasColumn('contact_logs','company_id') && $companyId) { $sqlFollow .= " AND company_id = ?"; $paramsFollow[] = $companyId; }
$q = $pdo->prepare($sqlFollow);
$q->execute($paramsFollow);
$followups_today = (int)$q->fetchColumn();
}
} catch (Exception $e) {}
try {
$callDateColumn = null;
if (tableHasColumn('contact_logs','contacted_at')) { $callDateColumn = 'contacted_at'; }
elseif (tableHasColumn('contact_logs','created_at')) { $callDateColumn = 'created_at'; }
if ($callDateColumn) {
$sqlCalls = "SELECT COUNT(*) FROM contact_logs WHERE DATE({$callDateColumn}) = CURDATE()";
$paramsCalls = [];
if (tableHasColumn('contact_logs','company_id') && $companyId) { $sqlCalls .= " AND company_id = ?"; $paramsCalls[] = $companyId; }
$stCalls = $pdo->prepare($sqlCalls);
$stCalls->execute($paramsCalls);
$calls_made_today = (int)$stCalls->fetchColumn();
}
} catch (Exception $e) {}
try {
$paymentDateColumn = function_exists('kpiPaymentDateColumn') ? kpiPaymentDateColumn('payments') : null;
if ($paymentDateColumn) {
$dealCountExpr = tableHasColumn('payments','deal_id') ? "COUNT(DISTINCT COALESCE(deal_id, id))" : "COUNT(*)";
$sqlDeals = "SELECT {$dealCountExpr} FROM payments WHERE DATE({$paymentDateColumn}) = CURDATE()";
$paramsDeals = [];
if (tableHasColumn('payments','status')) { $sqlDeals .= " AND LOWER(status) IN ('approved','paid','completed','successful')"; }
if (tableHasColumn('payments','company_id') && $companyId) { $sqlDeals .= " AND company_id = ?"; $paramsDeals[] = $companyId; }
$stDeals = $pdo->prepare($sqlDeals);
$stDeals->execute($paramsDeals);
$deals_closed_today = (int)$stDeals->fetchColumn();
}
} catch (Exception $e) {}
$upcoming_payments_7 = 0;
$upcoming_payments_7_amount = 0.0;
$payments_due_today = 0;
try {
$hasAlloc = $pdo->query("SHOW TABLES LIKE 'allocations'")->rowCount() > 0;
$hasPaySchedules = $pdo->query("SHOW TABLES LIKE 'payment_schedules'")->rowCount() > 0;
$hasInstallments = $pdo->query("SHOW TABLES LIKE 'installments'")->rowCount() > 0;
if ($hasPaySchedules && function_exists('tableHasColumn') && tableHasColumn('payment_schedules', 'due_date')) {
$amountDueCol = tableHasColumn('payment_schedules', 'amount_due') ? 'amount_due' : (tableHasColumn('payment_schedules', 'amount') ? 'amount' : null);
$amountPaidCol = tableHasColumn('payment_schedules', 'amount_paid') ? 'amount_paid' : null;
$statusCol = tableHasColumn('payment_schedules', 'status') ? 'status' : null;
$hasAllocId = tableHasColumn('payment_schedules', 'allocation_id');
$join = ($hasAlloc && $hasAllocId) ? " JOIN allocations a ON a.id = ps.allocation_id" : "";
$cmpClause = "";
$cmpParams = [];
if ($companyId && $hasAlloc && $hasAllocId && tableHasColumn('allocations', 'company_id')) { $cmpClause = " AND a.company_id = ?"; $cmpParams[] = $companyId; }
$where7 = "ps.due_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)";
if ($statusCol) { $where7 .= " AND LOWER(TRIM(ps.$statusCol)) IN ('pending','unpaid','due')"; }
if ($amountDueCol && $amountPaidCol) { $where7 .= " AND COALESCE(ps.$amountPaidCol,0) < COALESCE(ps.$amountDueCol,0)"; }
$amtExpr = $amountDueCol ? "COALESCE(ps.$amountDueCol,0)" : "0";
if ($amountPaidCol && $amountDueCol) { $amtExpr = "GREATEST(0, COALESCE(ps.$amountDueCol,0) - COALESCE(ps.$amountPaidCol,0))"; }
$st7 = $pdo->prepare("SELECT COUNT(*) c, COALESCE(SUM($amtExpr),0) s FROM payment_schedules ps$join WHERE $where7$cmpClause");
$st7->execute($cmpParams);
$r7 = $st7->fetch(PDO::FETCH_ASSOC) ?: [];
$upcoming_payments_7 = (int)($r7['c'] ?? 0);
$upcoming_payments_7_amount = (float)($r7['s'] ?? 0);
$where0 = "ps.due_date = CURDATE()";
if ($statusCol) { $where0 .= " AND LOWER(TRIM(ps.$statusCol)) IN ('pending','unpaid','due')"; }
if ($amountDueCol && $amountPaidCol) { $where0 .= " AND COALESCE(ps.$amountPaidCol,0) < COALESCE(ps.$amountDueCol,0)"; }
$st0 = $pdo->prepare("SELECT COUNT(*) FROM payment_schedules ps$join WHERE $where0$cmpClause");
$st0->execute($cmpParams);
$payments_due_today = (int)($st0->fetchColumn() ?: 0);
} elseif ($hasInstallments && function_exists('tableHasColumn') && tableHasColumn('installments', 'due_date')) {
$amountCol = tableHasColumn('installments', 'amount_due') ? 'amount_due' : (tableHasColumn('installments', 'amount') ? 'amount' : null);
$statusCol = tableHasColumn('installments', 'status') ? 'status' : null;
$hasAllocId = tableHasColumn('installments', 'allocation_id');
$join = ($hasAlloc && $hasAllocId) ? " JOIN allocations a ON a.id = i.allocation_id" : "";
$cmpClause = "";
$cmpParams = [];
if ($companyId && $hasAlloc && $hasAllocId && tableHasColumn('allocations', 'company_id')) { $cmpClause = " AND a.company_id = ?"; $cmpParams[] = $companyId; }
$where7 = "i.due_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)";
if ($statusCol) { $where7 .= " AND LOWER(TRIM(i.$statusCol)) IN ('pending','unpaid','due')"; }
$amtExpr = $amountCol ? "COALESCE(i.$amountCol,0)" : "0";
$st7 = $pdo->prepare("SELECT COUNT(*) c, COALESCE(SUM($amtExpr),0) s FROM installments i$join WHERE $where7$cmpClause");
$st7->execute($cmpParams);
$r7 = $st7->fetch(PDO::FETCH_ASSOC) ?: [];
$upcoming_payments_7 = (int)($r7['c'] ?? 0);
$upcoming_payments_7_amount = (float)($r7['s'] ?? 0);
$where0 = "i.due_date = CURDATE()";
if ($statusCol) { $where0 .= " AND LOWER(TRIM(i.$statusCol)) IN ('pending','unpaid','due')"; }
$st0 = $pdo->prepare("SELECT COUNT(*) FROM installments i$join WHERE $where0$cmpClause");
$st0->execute($cmpParams);
$payments_due_today = (int)($st0->fetchColumn() ?: 0);
}
} catch (Throwable $e) { $upcoming_payments_7 = 0; $upcoming_payments_7_amount = 0.0; $payments_due_today = 0; }
try {
if ($deals_closed_today === 0) {
$dealDateColumn = tableHasColumn('deals','created_at') ? 'created_at' : (tableHasColumn('deals','date') ? 'date' : null);
if ($dealDateColumn) {
$sqlDeals = "SELECT COUNT(*) FROM deals WHERE DATE({$dealDateColumn}) = CURDATE()";
$sqlDeals = "SELECT COUNT(*) FROM deals WHERE DATE({$dealDateColumn}) = CURDATE()";
$paramsDeals = [];
if (tableHasColumn('deals','status')) { $sqlDeals .= " AND LOWER(status) IN ('closed','completed','approved','sold','won')"; }
if (tableHasColumn('deals','company_id') && $companyId) { $sqlDeals .= " AND company_id = ?"; $paramsDeals[] = $companyId; }
$stDeals = $pdo->prepare($sqlDeals);
$stDeals->execute($paramsDeals);
$deals_closed_today = (int)$stDeals->fetchColumn();
}
}
} catch (Exception $e) {}
$client_reg_pending = 0;
try {
$statuses = ['draft','pending_verification','sent_to_accounts','payment_verified'];
$whereClientForms = "status IN ('" . implode("','", $statuses) . "')";
$sqlCR = "SELECT COUNT(*) FROM client_forms WHERE $whereClientForms";
$paramsCR = [];
if ($companyId && tableHasColumn('client_forms','company_id')) { $sqlCR .= " AND company_id = ?"; $paramsCR[] = $companyId; }
$stCR = $pdo->prepare($sqlCR);
$stCR->execute($paramsCR);
$client_reg_pending = (int)$stCR->fetchColumn();
} catch (Exception $e) {}
$recentLeads = [];
try {
$sqlL = "SELECT id, client_name, client_email, client_phone, status, agent_id, created_at FROM {$leadTable}";
$prL = [];
if ($companyId && tableHasColumn($leadTable,'company_id')) { $sqlL .= " WHERE company_id = ?"; $prL[] = $companyId; }
$sqlL .= " ORDER BY created_at DESC LIMIT 10";
$stL = $pdo->prepare($sqlL);
$stL->execute($prL);
$recentLeads = $stL->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {}
$callLogs = [];
try {
$callUserJoin = tableHasColumn('contact_logs','user_id') ? " LEFT JOIN users u ON u.id = c.user_id" : " LEFT JOIN users u ON 1=0";
$callDateExpr = tableHasColumn('contact_logs','contacted_at') ? "c.contacted_at" : (tableHasColumn('contact_logs','created_at') ? "c.created_at" : "NULL");
$callSelect = [
"c.id",
(tableHasColumn('contact_logs','note') ? "c.note" : "NULL") . " AS note",
(tableHasColumn('contact_logs','via') ? "c.via" : "NULL") . " AS via",
(tableHasColumn('contact_logs','type') ? "c.type" : "'open'") . " AS type",
(tableHasColumn('contact_logs','next_contact_at') ? "c.next_contact_at" : "NULL") . " AS next_contact_at",
(tableHasColumn('contact_logs','assigned_to') ? "c.assigned_to" : "NULL") . " AS assigned_to",
"u.name",
"u.phone",
"{$callDateExpr} AS activity_at"
];
$sqlC = "SELECT " . implode(", ", $callSelect) . " FROM contact_logs c" . $callUserJoin;
$paramsCallLogs = [];
if ($companyId && tableHasColumn('contact_logs','company_id')) { $sqlC .= " WHERE c.company_id = ?"; $paramsCallLogs[] = $companyId; }
$sqlC .= " ORDER BY " . ($callDateExpr === "NULL" ? "c.id" : $callDateExpr) . " DESC LIMIT 10";
$stC = $pdo->prepare($sqlC);
$stC->execute($paramsCallLogs);
$callLogs = $stC->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {}
$todayFups = [];
try {
if (tableHasColumn('contact_logs','next_contact_at')) {
$followJoin = tableHasColumn('contact_logs','user_id') ? " LEFT JOIN users u ON u.id = c.user_id" : " LEFT JOIN users u ON 1=0";
$sqlF = "SELECT c.id, " . (tableHasColumn('contact_logs','note') ? "c.note" : "NULL") . " AS note, c.next_contact_at, u.name, u.phone, u.email FROM contact_logs c" . $followJoin . " WHERE DATE(c.next_contact_at) = CURDATE()";
$paramsFollowList = [];
if ($companyId && tableHasColumn('contact_logs','company_id')) { $sqlF .= " AND c.company_id = ?"; $paramsFollowList[] = $companyId; }
$sqlF .= " ORDER BY c.next_contact_at ASC LIMIT 20";
$stF = $pdo->prepare($sqlF);
$stF->execute($paramsFollowList);
$todayFups = $stF->fetchAll(PDO::FETCH_ASSOC);
}
} catch (Exception $e) {}
$birthdayReminders = []; $anniversaryReminders = [];
try {
if (tableHasColumn('users','date_of_birth')) {
$birthdayAnniversarySelect = tableHasColumn('users','anniversary_date') ? 'anniversary_date' : "NULL AS anniversary_date";
$sqlB = $companyId && tableHasColumn('users','company_id')
? "SELECT id, name, email, date_of_birth, {$birthdayAnniversarySelect} FROM users WHERE role = 'client' AND company_id = ? AND date_of_birth IS NOT NULL AND date_of_birth <> ''"
: "SELECT id, name, email, date_of_birth, {$birthdayAnniversarySelect} FROM users WHERE role = 'client' AND date_of_birth IS NOT NULL AND date_of_birth <> ''";
$stB = $pdo->prepare($sqlB);
$stB->execute($companyId && tableHasColumn('users','company_id') ? [$companyId] : []);
foreach ($stB->fetchAll(PDO::FETCH_ASSOC) as $birthdayClient) {
$reminder = dashboardOccurrenceReminder($birthdayClient['date_of_birth'] ?? null);
if (!$reminder || $reminder['days_until'] < 0 || $reminder['days_until'] > 30) {
continue;
}
$birthdayReminders[] = [
'id' => $birthdayClient['id'] ?? null,
'name' => $birthdayClient['name'] ?? '-',
'email' => $birthdayClient['email'] ?? '',
'days_until' => $reminder['days_until'],
'occurs_on' => $reminder['occurs_on'],
'reminder_label' => $reminder['label'],
'timestamp' => $reminder['timestamp'],
'birthday_display' => dashboardDisplayDate($birthdayClient['date_of_birth'] ?? null),
'anniversary_display' => dashboardDisplayDate($birthdayClient['anniversary_date'] ?? null)
];
}
usort($birthdayReminders, function ($left, $right) {
$cmp = ((int)($left['days_until'] ?? 0)) <=> ((int)($right['days_until'] ?? 0));
if ($cmp !== 0) {
return $cmp;
}
return strcasecmp((string)($left['name'] ?? ''), (string)($right['name'] ?? ''));
});
$birthdays_upcoming = count($birthdayReminders);
}
} catch (Exception $e) {}
try {
if (tableHasColumn('users','anniversary_date')) {
$anniversaryBirthdaySelect = tableHasColumn('users','date_of_birth') ? 'date_of_birth' : "NULL AS date_of_birth";
$sqlA = $companyId && tableHasColumn('users','company_id')
? "SELECT id, name, email, anniversary_date, {$anniversaryBirthdaySelect} FROM users WHERE role = 'client' AND company_id = ? AND anniversary_date IS NOT NULL AND anniversary_date <> ''"
: "SELECT id, name, email, anniversary_date, {$anniversaryBirthdaySelect} FROM users WHERE role = 'client' AND anniversary_date IS NOT NULL AND anniversary_date <> ''";
$stA = $pdo->prepare($sqlA);
$stA->execute($companyId && tableHasColumn('users','company_id') ? [$companyId] : []);
foreach ($stA->fetchAll(PDO::FETCH_ASSOC) as $anniversaryClient) {
$reminder = dashboardOccurrenceReminder($anniversaryClient['anniversary_date'] ?? null);
if (!$reminder || $reminder['days_until'] < 0 || $reminder['days_until'] > 30) {
continue;
}
$anniversaryReminders[] = [
'id' => $anniversaryClient['id'] ?? null,
'name' => $anniversaryClient['name'] ?? '-',
'email' => $anniversaryClient['email'] ?? '',
'days_until' => $reminder['days_until'],
'occurs_on' => $reminder['occurs_on'],
'reminder_label' => $reminder['label'],
'timestamp' => $reminder['timestamp'],
'birthday_display' => dashboardDisplayDate($anniversaryClient['date_of_birth'] ?? null),
'anniversary_display' => dashboardDisplayDate($anniversaryClient['anniversary_date'] ?? null)
];
}
usort($anniversaryReminders, function ($left, $right) {
$cmp = ((int)($left['days_until'] ?? 0)) <=> ((int)($right['days_until'] ?? 0));
if ($cmp !== 0) {
return $cmp;
}
return strcasecmp((string)($left['name'] ?? ''), (string)($right['name'] ?? ''));
});
$anniversaries_upcoming = count($anniversaryReminders);
}
} catch (Exception $e) {}
$open_ticket_workload = (int)$counts['open'] + (int)$counts['pending'];
$celebrations_total = count($birthdayReminders) + count($anniversaryReminders);
$celebrationsPopupItems = [];
if ($celebrations_total > 0) {
foreach (array_slice($birthdayReminders, 0, 6) as $b) {
$celebrationsPopupItems[] = [
'type' => 'Birthday',
'name' => (string)($b['name'] ?? '-'),
'occurs_on' => (string)($b['occurs_on'] ?? '-'),
'label' => (string)($b['reminder_label'] ?? ''),
];
}
foreach (array_slice($anniversaryReminders, 0, 6) as $a) {
if (count($celebrationsPopupItems) >= 6) { break; }
$celebrationsPopupItems[] = [
'type' => 'Anniversary',
'name' => (string)($a['name'] ?? '-'),
'occurs_on' => (string)($a['occurs_on'] ?? '-'),
'label' => (string)($a['reminder_label'] ?? ''),
];
}
}
$recordDealHref = file_exists(__DIR__ . '/deal-submission.php') ? 'deal-submission.php' : (file_exists(__DIR__ . '/sales-revenue.php') ? 'sales-revenue.php' : 'transactions.php');
$hotLeadCount = max(1, min(3, $new_leads_today + $followups_today));
$suggestedActions = [];
if ($celebrations_total > 0) {
$celebrationMeta = [];
if ($birthdays_upcoming > 0) {
$celebrationMeta[] = $birthdays_upcoming . ' birthday reminder' . ($birthdays_upcoming === 1 ? '' : 's') . ' in 30 days';
}
if ($anniversaries_upcoming > 0) {
$celebrationMeta[] = $anniversaries_upcoming . ' anniversary reminder' . ($anniversaries_upcoming === 1 ? '' : 's') . ' in 30 days';
}
$suggestedActions[] = ['label' => 'Review client celebration reminders', 'meta' => implode(' · ', $celebrationMeta), 'href' => '#client-celebrations', 'accent' => 'success'];
}
if ($followups_today > 0 || $new_leads_today > 0) {
$suggestedActions[] = ['label' => 'Follow up on ' . $hotLeadCount . ' hot lead' . ($hotLeadCount === 1 ? '' : 's'), 'meta' => $followups_today . ' follow-up due today', 'href' => '#priority-tasks', 'accent' => 'warning'];
}
if ($open_ticket_workload > 0) {
$suggestedActions[] = ['label' => 'Clear the support queue', 'meta' => $open_ticket_workload . ' open or pending tickets', 'href' => '#workspace-tabs', 'accent' => 'orange'];
}
if ($my_assigned > 0) {
$suggestedActions[] = ['label' => 'Review your assigned queue', 'meta' => $my_assigned . ' tickets assigned to you', 'href' => '#workspace-tabs', 'accent' => 'primary'];
}
if (empty($suggestedActions)) {
$suggestedActions[] = ['label' => 'Log your next outreach touchpoint', 'meta' => 'Queue is clear for proactive engagement', 'href' => 'customer-care.php#logs', 'accent' => 'primary'];
}
$viewerId = (int)($_SESSION['user_id'] ?? 0);
$myClientsCount = 0;
$myAmountApproved = 0.0;
try {
if ($viewerId > 0 && function_exists('tableHasColumn') && tableHasColumn('payments','submitted_by_user')) {
$final = function_exists('kpiPaymentFinalizedStatuses') ? (array)kpiPaymentFinalizedStatuses() : ['verified','approved','paid','completed','success'];
$statusSql = function_exists('kpiSqlList') ? kpiSqlList($final) : "('verified','approved','paid','completed','success')";
$roleColOk = tableHasColumn('payments','submitted_by_role');
$clientIdCol = tableHasColumn('payments','client_id') ? 'client_id' : (tableHasColumn('payments','user_id') ? 'user_id' : '');
if ($clientIdCol !== '') {
$sql = "SELECT COUNT(DISTINCT COALESCE(NULLIF($clientIdCol,0), user_id)) AS c, COALESCE(SUM(amount),0) AS s
FROM payments
WHERE submitted_by_user = ?
AND status IN $statusSql";
$params = [$viewerId];
if ($roleColOk) { $sql .= " AND (submitted_by_role IS NULL OR submitted_by_role = '' OR submitted_by_role IN ('contact_centre','contact','customer_rep','contact_rep'))"; }
if (tableHasColumn('payments','company_id') && $companyId) { $sql .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyId; }
if (tableHasColumn('payments','reference')) { $sql .= " AND reference LIKE 'deal-submission-%'"; }
$st = $pdo->prepare($sql);
$st->execute($params);
$row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
$myClientsCount = (int)($row['c'] ?? 0);
$myAmountApproved = (float)($row['s'] ?? 0);
}
}
} catch (Throwable $e) {}
$performanceCards = [
[
'label' => 'Calls Made Today',
'value' => $calls_made_today,
'icon' => 'fa-solid fa-phone-volume',
'href' => 'customer-care.php#logs',
'accent' => 'primary',
'insight' => $calls_made_today > 0 ? 'Outbound activity is already moving across the queue.' : 'No calls logged yet. Start with the highest-priority conversations.'
],
[
'label' => 'Deals Closed Today',
'value' => $deals_closed_today,
'icon' => 'fa-solid fa-handshake',
'href' => $recordDealHref,
'accent' => 'warning',
'insight' => $deals_closed_today > 0 ? 'Closed outcomes are being captured in today’s workflow.' : 'No deals closed yet. Keep active opportunities moving forward.'
],
[
'label' => 'Approved Revenue',
'value' => $myAmountApproved,
'value_html' => '₦' . number_format((float)$myAmountApproved, 2),
'icon' => 'fa-solid fa-naira-sign',
'href' => 'transactions.php',
'accent' => 'info',
'insight' => $myAmountApproved > 0 ? 'Approved payments credited to your submissions.' : 'No approved payments credited yet. Keep following up on submitted deals.'
],
[
'label' => 'My Clients',
'value' => $myClientsCount,
'icon' => 'fa-solid fa-users',
'href' => 'deal-submission.php',
'accent' => 'success',
'insight' => $myClientsCount > 0 ? 'Client portfolio reflects submissions credited to your account.' : 'No submitted clients recorded yet. Submit a deal to start tracking your portfolio.'
],
];
$celebrationCards = [
[
'label' => 'Birthdays ≤30D',
'value' => $birthdays_upcoming,
'icon' => 'fa-solid fa-cake-candles',
'href' => '#client-celebrations',
'accent' => 'success',
'insight' => $birthdays_upcoming > 0 ? 'Upcoming birthday reminders help the team plan proactive outreach.' : 'No client birthdays are due within the next 30 days.'
],
[
'label' => 'Anniversaries ≤30D',
'value' => $anniversaries_upcoming,
'icon' => 'fa-solid fa-heart',
'href' => '#client-celebrations',
'accent' => 'info',
'insight' => $anniversaries_upcoming > 0 ? 'Upcoming anniversaries create warm relationship follow-up moments.' : 'No client anniversaries are due within the next 30 days.'
],
];
$dealOverdueCount = 0;
$dealOverdueAmount = 0.0;
$dealOverduePenalty = 0.0;
try {
$dealDue = function_exists('kpiDealPaymentDueSummary')
? kpiDealPaymentDueSummary($pdo, $companyId, true, null)
: ['overdue_count' => 0, 'overdue_amount' => 0.0, 'overdue_penalty' => 0.0];
$dealOverdueCount = (int)($dealDue['overdue_count'] ?? 0);
$dealOverdueAmount = (float)($dealDue['overdue_amount'] ?? 0);
$dealOverduePenalty = (float)($dealDue['overdue_penalty'] ?? 0);
} catch (Throwable $e) {}
$over30Count = 0;
$over30Amount = 0.0;
try {
if (function_exists('kpiOverdueInstallmentsAgingSummary')) {
$s1 = kpiOverdueInstallmentsAgingSummary($pdo, $companyId, true, 30);
$over30Count += (int)($s1['count'] ?? 0);
$over30Amount += (float)($s1['amount'] ?? 0);
}
if (function_exists('kpiOverduePaymentSchedulesAgingSummary')) {
$s2 = kpiOverduePaymentSchedulesAgingSummary($pdo, $companyId, true, 30);
$over30Count += (int)($s2['count'] ?? 0);
$over30Amount += (float)($s2['amount'] ?? 0);
}
} catch (Throwable $e) {}
$over30Penalty = round($over30Amount * 0.05, 2);
$procOpenCount = 0;
try {
$viewerId = (int)($_SESSION['user_id'] ?? 0);
if ($viewerId > 0 && $pdo->query("SHOW TABLES LIKE 'procurement_requests'")->rowCount() > 0) {
$sql = "SELECT COUNT(*) FROM procurement_requests WHERE requested_by = ? AND LOWER(TRIM(status)) NOT IN ('approved','rejected','converted_to_po')";
$params = [$viewerId];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('procurement_requests', 'company_id')) {
$sql .= " AND (company_id = ? OR company_id IS NULL)";
$params[] = $companyId;
}
$st = $pdo->prepare($sql);
$st->execute($params);
$procOpenCount = (int)($st->fetchColumn() ?: 0);
}
} catch (Throwable $e) { $procOpenCount = 0; }
$systemCards = [
[
'label' => 'Resolved Today',
'value' => $resolved_today,
'icon' => 'fa-solid fa-circle-check',
'href' => 'customer-care.php#tickets',
'accent' => 'success',
'insight' => $resolved_today > 0 ? 'Resolved tickets show healthy service throughput.' : 'No tickets have been resolved yet today.'
],
[
'label' => 'Assigned to You',
'value' => $my_assigned,
'icon' => 'fa-solid fa-user-check',
'href' => 'customer-rep-dashboard.php?assigned_to=me#workspace-tabs',
'accent' => 'primary',
'insight' => $my_assigned > 0 ? 'Personal queue items still need attention.' : 'Your assigned support queue is currently clear.'
],
[
'label' => 'Deal Payments Overdue',
'value' => $dealOverdueCount,
'icon' => 'fa-solid fa-triangle-exclamation',
'href' => 'clients.php?view=overdue',
'accent' => $dealOverdueCount > 0 ? 'danger' : 'info',
'insight' => $dealOverdueCount > 0 ? ('₦' . number_format($dealOverdueAmount + $dealOverduePenalty, 2) . ' in deal payments are overdue (incl. 5% penalty). Immediate follow-up required.') : 'No overdue deal payments detected.'
],
[
'label' => '30+ Days Overdue',
'value' => $over30Count,
'icon' => 'fa-solid fa-skull-crossbones',
'href' => 'finance-installments.php',
'accent' => $over30Count > 0 ? 'danger' : 'info',
'insight' => $over30Count > 0 ? ('High-risk defaults: ₦' . number_format($over30Amount + $over30Penalty, 2) . ' outstanding (incl. 5% penalty). Immediate escalation required.') : 'No 30+ day overdue installments detected.'
],
[
'label' => 'Payments Due Today',
'value' => $payments_due_today,
'icon' => 'fa-solid fa-calendar-day',
'href' => 'clients.php?view=due_today',
'accent' => $payments_due_today > 0 ? 'warning' : 'info',
'insight' => $payments_due_today > 0 ? 'Installments due today need proactive outreach to avoid penalties.' : 'No installment payments are due today.'
],
];
$dailyWorkCards = [
[
'label' => 'New Leads',
'value' => $new_leads_today,
'icon' => 'fa-solid fa-user-plus',
'href' => 'agent-leads.php',
'accent' => 'primary',
'insight' => $new_leads_today > 0 ? 'Fresh pipeline is ready for outreach.' : 'No new leads yet. Watch incoming forms.'
],
[
'label' => 'Follow-ups Today',
'value' => $followups_today,
'icon' => 'fa-regular fa-clock',
'href' => 'customer-care.php?task_range=today#tasks',
'accent' => 'warning',
'insight' => $followups_today > 0 ? 'Priority outreach is waiting in the queue.' : 'Follow-up queue is fully under control.'
],
[
'label' => 'Payments Due ≤7D',
'value' => $upcoming_payments_7,
'icon' => 'fa-solid fa-bell',
'href' => 'clients.php?view=due_7d',
'accent' => $upcoming_payments_7 > 0 ? 'warning' : 'info',
'insight' => $upcoming_payments_7 > 0 ? ('Upcoming payment follow-ups: ₦' . number_format($upcoming_payments_7_amount, 2) . ' expected within 7 days.') : 'No upcoming installment payments due within 7 days.'
],
[
'label' => 'Pending Registrations',
'value' => $client_reg_pending,
'icon' => 'fa-solid fa-file-signature',
'href' => 'client-registrations.php',
'accent' => 'success',
'insight' => $client_reg_pending > 0 ? 'Registrations are waiting for processing or verification.' : 'Registration pipeline is caught up.'
],
[
'label' => 'Open Support Tickets',
'value' => $open_ticket_workload,
'icon' => 'fa-solid fa-ticket',
'href' => 'customer-care.php#tickets',
'accent' => 'info',
'insight' => $open_ticket_workload > 0 ? 'Active support issues need queue attention.' : 'Ticket queue is currently clear.'
],
[
'label' => 'Purchase Requests',
'value' => $procOpenCount,
'icon' => 'fa-solid fa-cart-shopping',
'href' => 'procurement-requests.php',
'accent' => $procOpenCount > 0 ? 'warning' : 'info',
'insight' => $procOpenCount > 0 ? 'Purchase requests are waiting in your queue.' : 'No open purchase requests assigned to you.'
],
];
$priorityTasks = [
[
'label' => 'Follow-ups requiring attention',
'value' => $followups_today,
'href' => 'customer-care.php?task_range=today#tasks',
'accent' => $followups_today > 0 ? 'red' : 'green',
'detail' => $followups_today > 0 ? 'Action needed before the day closes' : 'No urgent follow-ups pending'
],
[
'label' => 'New leads not contacted',
'value' => $new_leads_today,
'href' => 'agent-leads.php',
'accent' => $new_leads_today > 0 ? 'yellow' : 'green',
'detail' => $new_leads_today > 0 ? 'Fresh inquiries are waiting for first response' : 'All new leads are already covered'
],
[
'label' => 'Open tickets',
'value' => $open_ticket_workload,
'href' => 'customer-care.php#tickets',
'accent' => $open_ticket_workload > 0 ? 'red' : 'green',
'detail' => $open_ticket_workload > 0 ? 'Open or pending tickets need review' : 'Support queue is currently clear'
],
];
?>
<style>
.cc-workspace {
padding-top: 1.75rem;
padding-bottom: 2rem;
}
.cc-performance-shell {
background: linear-gradient(135deg, #ffffff, #f8fbff);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 30px;
padding: 2.25rem;
color: #0f172a;
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.08);
position: relative;
overflow: hidden;
}
.cc-performance-shell:before,
.cc-performance-shell:after {
content: "";
position: absolute;
border-radius: 999px;
background: rgba(255,255,255,0.08);
}
.cc-performance-shell:before {
width: 260px;
height: 260px;
top: -120px;
right: -90px;
background: rgba(37, 99, 235, 0.08);
}
.cc-performance-shell:after {
width: 200px;
height: 200px;
bottom: -90px;
left: -70px;
background: rgba(14, 165, 233, 0.08);
}
.cc-performance-shell > * {
position: relative;
z-index: 1;
}
.cc-page-label {
display: inline-flex;
align-items: center;
gap: .55rem;
padding: .5rem .85rem;
border-radius: 999px;
background: rgba(37, 99, 235, 0.08);
color: #1d4ed8;
font-size: .85rem;
font-weight: 600;
letter-spacing: .02em;
}
.cc-performance-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1.5rem;
margin-bottom: 1.75rem;
}
.cc-performance-header h1 {
font-size: clamp(2rem, 3vw, 2.85rem);
line-height: 1.05;
letter-spacing: -.03em;
margin: .9rem 0 .55rem;
}
.cc-performance-copy {
max-width: 760px;
color: #475569;
font-size: 1rem;
}
.cc-grid {
margin-top: 2rem;
}
.cc-stat-sections {
display: flex;
flex-direction: column;
gap: 1.35rem;
}
.cc-stat-section {
border: 1px solid rgba(148, 163, 184, 0.16);
border-radius: 28px;
padding: 1.45rem;
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.07);
}
.cc-section-performance { background: linear-gradient(135deg, rgba(219, 234, 254, 0.9), rgba(255, 255, 255, 0.98)); }
.cc-section-celebrations { background: linear-gradient(135deg, rgba(236, 253, 245, 0.92), rgba(255, 255, 255, 0.98)); }
.cc-section-system { background: linear-gradient(135deg, rgba(248, 250, 252, 0.98), rgba(241, 245, 249, 0.96)); }
.cc-section-daily { background: linear-gradient(135deg, rgba(239, 246, 255, 0.9), rgba(255, 255, 255, 0.98)); }
.cc-stat-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.1rem;
}
.cc-stat-header h2 {
margin: 0;
font-size: 1.05rem;
font-weight: 800;
color: #0f172a;
}
.cc-stat-header p {
margin: .28rem 0 0;
color: #64748b;
font-size: .9rem;
}
.cc-card-grid {
display: grid;
gap: 1rem;
}
.cc-card-grid-large {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.cc-card-grid-medium {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.cc-card-grid-compact {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.cc-card {
display: block;
background: #fff;
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 26px;
padding: 1.55rem;
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.08);
color: inherit;
text-decoration: none;
transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}
.cc-card-large {
padding: 1.85rem;
min-height: 210px;
}
.cc-card-medium {
min-height: 186px;
}
.cc-card-compact {
padding: 1.2rem;
min-height: 150px;
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.06);
}
.cc-card:hover {
transform: translateY(-5px);
box-shadow: 0 24px 50px rgba(15, 23, 42, 0.12);
border-color: rgba(59, 130, 246, 0.22);
color: inherit;
}
.cc-card-icon {
width: 56px;
height: 56px;
border-radius: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
}
.cc-card-label {
font-size: .8rem;
text-transform: uppercase;
letter-spacing: .08em;
color: #64748b;
font-weight: 700;
}
.cc-card-value {
font-size: 2.2rem;
line-height: 1;
font-weight: 800;
color: #020617;
margin: 1rem 0 .55rem;
}
.cc-card-insight {
color: #64748b;
font-size: .93rem;
line-height: 1.5;
}
.cc-card-large .cc-card-value {
font-size: 2.6rem;
}
.cc-card-compact .cc-card-value {
font-size: 1.7rem;
margin: .7rem 0 .35rem;
}
.cc-card-compact .cc-card-label {
font-size: .74rem;
}
.cc-card-compact .cc-card-insight {
font-size: .84rem;
line-height: 1.4;
}
.cc-accent-primary { color: #2563eb; background: rgba(37, 99, 235, 0.12); }
.cc-accent-info { color: #0891b2; background: rgba(6, 182, 212, 0.12); }
.cc-accent-success { color: #059669; background: rgba(16, 185, 129, 0.12); }
.cc-accent-warning { color: #d97706; background: rgba(245, 158, 11, 0.16); }
.cc-section {
background: #fff;
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 28px;
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.07);
padding: 1.5rem;
}
.cc-performance-body {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 1.5rem;
align-items: start;
}
.cc-performance-main {
min-width: 0;
}
.cc-performance-side {
min-width: 0;
}
.cc-section-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.25rem;
}
.cc-section-title h2,
.cc-section-title h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 700;
color: #0f172a;
}
.cc-section-subtitle {
margin: .3rem 0 0;
color: #64748b;
font-size: .92rem;
}
.cc-priority-strip {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
}
.cc-priority-item {
display: block;
padding: 1.1rem 1.2rem;
border-radius: 20px;
text-decoration: none;
color: #0f172a;
background: #f8fafc;
border: 1px solid rgba(148, 163, 184, 0.16);
transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}
.cc-priority-item:hover {
transform: translateY(-3px);
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.1);
border-color: rgba(59, 130, 246, 0.24);
color: #0f172a;
}
.cc-priority-head {
display: flex;
align-items: center;
gap: .7rem;
margin-bottom: .8rem;
}
.cc-priority-dot {
width: .85rem;
height: .85rem;
border-radius: 999px;
box-shadow: 0 0 0 6px rgba(15, 23, 42, 0.04);
flex-shrink: 0;
}
.cc-priority-red { background: #ef4444; }
.cc-priority-yellow { background: #f59e0b; }
.cc-priority-green { background: #10b981; }
.cc-priority-label {
font-size: .94rem;
font-weight: 700;
color: #0f172a;
}
.cc-priority-value {
font-size: 1.75rem;
font-weight: 800;
line-height: 1;
margin-bottom: .35rem;
}
.cc-priority-detail {
color: #64748b;
font-size: .9rem;
line-height: 1.45;
}
.cc-tabs {
display: flex;
flex-wrap: wrap;
gap: .75rem;
}
.cc-tab-button {
border: 0;
background: #eef2ff;
color: #334155;
border-radius: 999px;
padding: .72rem 1.1rem;
font-weight: 700;
transition: background .2s ease, color .2s ease, transform .2s ease;
}
.cc-tab-button:hover {
background: #dbeafe;
color: #0f172a;
transform: translateY(-1px);
}
.cc-tab-button.active {
background: linear-gradient(135deg, #1d4ed8, #1e40af);
color: #fff;
box-shadow: 0 14px 30px rgba(29, 78, 216, 0.24);
}
.cc-tab-panel {
margin-top: 1.35rem;
}
.cc-tab-shell {
border: 1px solid rgba(148, 163, 184, 0.16);
border-radius: 22px;
overflow: hidden;
}
.cc-tab-shell .card-header {
background: #fff;
border-bottom: 1px solid rgba(148, 163, 184, 0.16);
padding: 1rem 1.2rem;
}
.cc-tab-shell .card-body {
padding: 0;
}
.cc-table {
margin: 0;
}
.cc-table thead th {
font-size: .76rem;
text-transform: uppercase;
letter-spacing: .06em;
color: #64748b;
background: #f8fafc;
border-bottom-width: 1px;
padding: .95rem 1rem;
}
.cc-table tbody td {
padding: 1rem;
vertical-align: middle;
border-color: rgba(148, 163, 184, 0.12);
}
.cc-table tbody tr:hover {
background: rgba(248, 250, 252, 0.85);
}
.cc-client-cell strong {
display: block;
color: #0f172a;
}
.cc-client-cell span {
display: block;
margin-top: .2rem;
color: #64748b;
font-size: .88rem;
}
.cc-status-pill {
display: inline-flex;
align-items: center;
padding: .4rem .75rem;
border-radius: 999px;
font-size: .76rem;
font-weight: 700;
text-transform: capitalize;
}
.cc-status-open,
.cc-status-new { background: rgba(37, 99, 235, 0.12); color: #1d4ed8; }
.cc-status-pending,
.cc-status-medium { background: rgba(245, 158, 11, 0.16); color: #b45309; }
.cc-status-resolved,
.cc-status-closed { background: rgba(16, 185, 129, 0.14); color: #047857; }
.cc-status-high,
.cc-status-urgent { background: rgba(239, 68, 68, 0.14); color: #b91c1c; }
.cc-side-stack {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.cc-side-panel {
position: sticky;
top: 1.5rem;
}
.cc-side-panel .cc-section-title {
margin-bottom: 1rem;
}
.cc-celebration-list,
.cc-suggested-list {
display: flex;
flex-direction: column;
gap: .9rem;
}
.cc-celebration-item,
.cc-suggested-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: .9rem;
padding: 1rem;
border-radius: 18px;
background: #f8fafc;
}
.cc-suggested-item {
text-decoration: none;
color: inherit;
transition: transform .2s ease, background .2s ease;
}
.cc-suggested-item:hover {
transform: translateX(4px);
background: #eff6ff;
color: inherit;
}
.cc-suggested-badge {
min-width: 2.2rem;
height: 2.2rem;
border-radius: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: .9rem;
flex-shrink: 0;
}
.cc-suggested-badge.primary { color: #2563eb; background: rgba(37,99,235,0.12); }
.cc-suggested-badge.warning { color: #d97706; background: rgba(245,158,11,0.18); }
.cc-suggested-badge.success { color: #059669; background: rgba(16,185,129,0.12); }
.cc-suggested-badge.orange { color: #c2410c; background: rgba(249,115,22,0.14); }
.cc-reminder-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: .4rem;
}
.cc-reminder-chip {
display: inline-flex;
align-items: center;
padding: .35rem .7rem;
border-radius: 999px;
background: rgba(37, 99, 235, 0.1);
color: #1d4ed8;
font-size: .74rem;
font-weight: 700;
white-space: nowrap;
}
.cc-client-link {
padding: 0;
border: 0;
background: transparent;
color: #0f172a;
font-weight: 700;
text-align: left;
transition: color .2s ease;
}
.cc-client-link:hover {
color: #1d4ed8;
}
.cc-client-link:focus-visible {
outline: 2px solid rgba(37, 99, 235, 0.25);
outline-offset: 4px;
border-radius: 8px;
}
.cc-date-detail {
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 18px;
padding: 1rem;
background: #f8fafc;
}
.cc-date-detail-label {
font-size: .78rem;
text-transform: uppercase;
letter-spacing: .06em;
color: #64748b;
font-weight: 700;
margin-bottom: .35rem;
}
.cc-date-detail-value {
color: #0f172a;
font-size: 1rem;
font-weight: 700;
}
.cc-quick-actions {
background: rgba(255,255,255,0.96);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 24px;
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.1);
padding: 1rem;
}
.cc-quick-actions .btn {
border-radius: 16px;
font-weight: 700;
}
.cc-quick-actions .btn-primary {
padding-top: .8rem;
padding-bottom: .8rem;
box-shadow: 0 14px 24px rgba(37, 99, 235, 0.18);
}
.cc-quick-actions .btn-outline-primary,
.cc-quick-actions .btn-outline-secondary {
background: #fff;
color: #475569;
border-color: rgba(148, 163, 184, 0.28);
padding-top: .72rem;
padding-bottom: .72rem;
}
.cc-quick-actions .btn-outline-primary:hover,
.cc-quick-actions .btn-outline-secondary:hover {
background: #f8fafc;
color: #0f172a;
border-color: rgba(100, 116, 139, 0.38);
}
.cc-empty-state {
padding: 2.5rem 1.5rem;
text-align: center;
color: #64748b;
}
@media (max-width: 1199.98px) {
.cc-side-panel {
position: static;
}
.cc-performance-body {
grid-template-columns: 1fr;
}
}
@media (max-width: 991.98px) {
.cc-performance-shell {
padding: 1.75rem;
}
.cc-performance-header {
flex-direction: column;
}
.cc-card-grid-large,
.cc-card-grid-medium,
.cc-card-grid-compact {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.cc-priority-strip {
grid-template-columns: 1fr;
}
}
@media (max-width: 767.98px) {
.cc-workspace {
padding-bottom: 1.5rem;
}
.cc-card-grid-large,
.cc-card-grid-medium,
.cc-card-grid-compact {
grid-template-columns: 1fr;
}
.cc-stat-section,
.cc-section,
.cc-quick-actions {
border-radius: 24px;
}
}
</style>
<div class="container-fluid px-4 cc-workspace">
<?php if (isset($_GET['notice']) && $_GET['notice'] !== ''): ?>
<div class="alert alert-<?= htmlspecialchars($_GET['type'] ?? 'info') ?> border-0 shadow-sm mb-4"><?= htmlspecialchars($_GET['notice']) ?></div>
<?php endif; ?>
<?php if (!empty($over30Count) && (int)$over30Count > 0): ?>
<div class="alert alert-danger alert-dismissible fade show border-0 shadow-sm mb-4" role="alert">
<div class="d-flex align-items-center">
<div class="fs-3 me-3"><i class="fa-solid fa-triangle-exclamation"></i></div>
<div>
<div class="fw-bold">30+ Days Overdue Reminder</div>
<div class="small">High-risk overdue schedules: <?= number_format((int)$over30Count) ?> item(s) • ₦<?= number_format((float)$over30Amount + (float)$over30Penalty, 2) ?> exposure (incl. penalty). Escalate immediately.</div>
</div>
<div class="ms-auto">
<a href="finance-installments.php" class="btn btn-sm btn-dark">View Installments</a>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<section class="cc-performance-shell">
<div class="cc-performance-header">
<div>
<span class="cc-page-label"><i class="fa-solid fa-bolt"></i>Contact Centre Dashboard</span>
<h1>Today's Performance</h1>
<p class="cc-performance-copy mb-0">Work through performance, celebrations, system visibility, and daily execution from one organized contact centre workspace.</p>
</div>
</div>
<div class="cc-stat-sections">
<section class="cc-stat-section cc-section-performance">
<div class="cc-stat-header">
<div>
<h2>Performance</h2>
<p>Top-line productivity indicators presented as the largest cards.</p>
</div>
</div>
<div class="cc-card-grid cc-card-grid-large">
<?php foreach ($performanceCards as $card): ?>
<a class="cc-card cc-card-large h-100" href="<?= htmlspecialchars($card['href']) ?>">
<div class="d-flex justify-content-between align-items-start gap-3">
<div>
<div class="cc-card-label"><?= htmlspecialchars($card['label']) ?></div>
<div class="cc-card-value"><?= isset($card['value_html']) ? $card['value_html'] : number_format((int)$card['value']) ?></div>
<div class="cc-card-insight"><?= htmlspecialchars($card['insight']) ?></div>
</div>
<span class="cc-card-icon cc-accent-<?= htmlspecialchars($card['accent']) ?>"><i class="<?= htmlspecialchars($card['icon']) ?>"></i></span>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
<section class="cc-stat-section cc-section-celebrations">
<div class="cc-stat-header">
<div>
<h2>Celebrations</h2>
<p>Personal outreach opportunities stay grouped and easy to scan.</p>
</div>
</div>
<div class="cc-card-grid cc-card-grid-large">
<?php foreach ($celebrationCards as $card): ?>
<a class="cc-card cc-card-medium h-100" href="<?= htmlspecialchars($card['href']) ?>">
<div class="d-flex justify-content-between align-items-start gap-3">
<div>
<div class="cc-card-label"><?= htmlspecialchars($card['label']) ?></div>
<div class="cc-card-value"><?= number_format((int)$card['value']) ?></div>
<div class="cc-card-insight"><?= htmlspecialchars($card['insight']) ?></div>
</div>
<span class="cc-card-icon cc-accent-<?= htmlspecialchars($card['accent']) ?>"><i class="<?= htmlspecialchars($card['icon']) ?>"></i></span>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
<section class="cc-stat-section cc-section-system">
<div class="cc-stat-header">
<div>
<h2>System Info</h2>
<p>Smaller operational health signals sit in their own compact row.</p>
</div>
</div>
<div class="cc-card-grid cc-card-grid-compact">
<?php foreach ($systemCards as $card): ?>
<a class="cc-card cc-card-compact h-100" href="<?= htmlspecialchars($card['href']) ?>">
<div class="d-flex justify-content-between align-items-start gap-3">
<div>
<div class="cc-card-label"><?= htmlspecialchars($card['label']) ?></div>
<div class="cc-card-value"><?= number_format((int)$card['value']) ?></div>
<div class="cc-card-insight"><?= htmlspecialchars($card['insight']) ?></div>
</div>
<span class="cc-card-icon cc-accent-<?= htmlspecialchars($card['accent']) ?>"><i class="<?= htmlspecialchars($card['icon']) ?>"></i></span>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
<section class="cc-stat-section cc-section-daily">
<div class="cc-stat-header">
<div>
<h2>Daily Work</h2>
<p>Current execution queues remain visible as medium-sized action cards.</p>
</div>
</div>
<div class="cc-card-grid cc-card-grid-medium">
<?php foreach ($dailyWorkCards as $card): ?>
<a class="cc-card cc-card-medium h-100" href="<?= htmlspecialchars($card['href']) ?>">
<div class="d-flex justify-content-between align-items-start gap-3">
<div>
<div class="cc-card-label"><?= htmlspecialchars($card['label']) ?></div>
<div class="cc-card-value"><?= number_format((int)$card['value']) ?></div>
<div class="cc-card-insight"><?= htmlspecialchars($card['insight']) ?></div>
</div>
<span class="cc-card-icon cc-accent-<?= htmlspecialchars($card['accent']) ?>"><i class="<?= htmlspecialchars($card['icon']) ?>"></i></span>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
</div>
</section>
<div class="row g-4 cc-grid">
<div class="col-12">
<div class="cc-performance-body">
<div class="cc-performance-main">
<section class="cc-section mb-4" id="priority-tasks">
<div class="cc-section-title">
<div>
<h2>Priority Tasks</h2>
<p class="cc-section-subtitle">See the most important actions first and move straight into the active queue.</p>
</div>
</div>
<div class="cc-priority-strip">
<?php foreach ($priorityTasks as $task): ?>
<a class="cc-priority-item" href="<?= htmlspecialchars($task['href']) ?>">
<div class="cc-priority-head">
<span class="cc-priority-dot cc-priority-<?= htmlspecialchars($task['accent']) ?>"></span>
<span class="cc-priority-label"><?= htmlspecialchars($task['label']) ?></span>
</div>
<div class="cc-priority-value"><?= number_format((int)$task['value']) ?></div>
<div class="cc-priority-detail"><?= htmlspecialchars($task['detail']) ?></div>
</a>
<?php endforeach; ?>
</div>
</section>
<section class="cc-section" id="workspace-tabs">
<div class="cc-section-title">
<div>
<h2>Operations Workspace</h2>
<p class="cc-section-subtitle">Switch between leads, call activity, and support tickets without leaving the page.</p>
</div>
<div class="cc-tabs" role="tablist" aria-label="Contact centre workspace tabs">
<button class="cc-tab-button active" type="button" data-workspace-tab="leads">Leads</button>
<button class="cc-tab-button" type="button" data-workspace-tab="calls">Calls</button>
<button class="cc-tab-button" type="button" data-workspace-tab="tickets">Tickets</button>
</div>
</div>
<div id="workspaceTabContent" class="cc-tab-panel"></div>
</section>
</div>
<div class="cc-performance-side">
<div class="cc-side-panel">
<div class="cc-side-stack">
<section class="cc-section">
<div class="cc-section-title">
<div>
<h3>Suggested Actions</h3>
<p class="cc-section-subtitle">Recommended next moves based on today's queue.</p>
</div>
</div>
<div class="cc-suggested-list">
<?php foreach ($suggestedActions as $action): ?>
<a class="cc-suggested-item" href="<?= htmlspecialchars($action['href']) ?>">
<span class="cc-suggested-badge <?= htmlspecialchars($action['accent']) ?>"><i class="fa-solid fa-arrow-trend-up"></i></span>
<span class="flex-grow-1">
<span class="fw-semibold d-block text-dark"><?= htmlspecialchars($action['label']) ?></span>
<span class="small text-muted d-block mt-1"><?= htmlspecialchars($action['meta']) ?></span>
</span>
<i class="fa-solid fa-chevron-right text-muted small mt-1"></i>
</a>
<?php endforeach; ?>
</div>
</section>
<section class="cc-section" id="client-celebrations">
<div class="cc-section-title">
<div>
<h3>Client Celebrations</h3>
<p class="cc-section-subtitle">Birthday and anniversary reminders for the next 30 days.</p>
</div>
<span class="badge text-bg-light rounded-pill px-3 py-2"><?= number_format($celebrations_total) ?> alerts</span>
</div>
<div class="cc-celebration-list">
<?php if (empty($birthdayReminders) && empty($anniversaryReminders)): ?>
<div class="cc-celebration-item">
<div>
<div class="fw-semibold text-dark">No celebration reminders scheduled</div>
<div class="small text-muted">Use the extra time for proactive check-ins and pipeline follow-ups.</div>
</div>
</div>
<?php endif; ?>
<?php foreach (array_slice($birthdayReminders, 0, 5) as $b): ?>
<div class="cc-celebration-item">
<div>
<button
type="button"
class="cc-client-link"
data-bs-toggle="modal"
data-bs-target="#celebrationClientModal"
data-client-name="<?= htmlspecialchars($b['name'] ?? '-', ENT_QUOTES) ?>"
data-client-email="<?= htmlspecialchars($b['email'] ?? '', ENT_QUOTES) ?>"
data-client-birthday="<?= htmlspecialchars($b['birthday_display'] ?? 'Not available', ENT_QUOTES) ?>"
data-client-anniversary="<?= htmlspecialchars($b['anniversary_display'] ?? 'Not available', ENT_QUOTES) ?>"
><?= htmlspecialchars($b['name'] ?? '-') ?></button>
<div class="small text-muted">Birthday reminder · <?= htmlspecialchars($b['occurs_on'] ?? '-') ?></div>
</div>
<div class="cc-reminder-meta">
<span class="cc-reminder-chip"><?= htmlspecialchars($b['reminder_label'] ?? '') ?></span>
<div class="small text-muted text-end"><?= htmlspecialchars($b['email'] ?? '') ?></div>
</div>
</div>
<?php endforeach; ?>
<?php foreach (array_slice($anniversaryReminders, 0, 5) as $a): ?>
<div class="cc-celebration-item">
<div>
<button
type="button"
class="cc-client-link"
data-bs-toggle="modal"
data-bs-target="#celebrationClientModal"
data-client-name="<?= htmlspecialchars($a['name'] ?? '-', ENT_QUOTES) ?>"
data-client-email="<?= htmlspecialchars($a['email'] ?? '', ENT_QUOTES) ?>"
data-client-birthday="<?= htmlspecialchars($a['birthday_display'] ?? 'Not available', ENT_QUOTES) ?>"
data-client-anniversary="<?= htmlspecialchars($a['anniversary_display'] ?? 'Not available', ENT_QUOTES) ?>"
><?= htmlspecialchars($a['name'] ?? '-') ?></button>
<div class="small text-muted">Anniversary reminder · <?= htmlspecialchars($a['occurs_on'] ?? '-') ?></div>
</div>
<div class="cc-reminder-meta">
<span class="cc-reminder-chip"><?= htmlspecialchars($a['reminder_label'] ?? '') ?></span>
<div class="small text-muted text-end"><?= htmlspecialchars($a['email'] ?? '') ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<div class="cc-quick-actions">
<div class="fw-semibold text-dark mb-2">Quick Actions</div>
<p class="small text-muted mb-3">Keep high-frequency actions within one click.</p>
<div class="d-grid gap-2">
<a href="customer-care.php#logs" class="btn btn-primary btn-lg"><i class="fa-solid fa-phone me-2"></i>Log Call</a>
<a href="agent-leads.php" class="btn btn-outline-primary btn-sm"><i class="fa-solid fa-user-plus me-2"></i>Add Lead</a>
<a href="<?= htmlspecialchars($recordDealHref) ?>" class="btn btn-outline-secondary btn-sm"><i class="fa-solid fa-handshake me-2"></i>Record Deal</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php if ($celebrations_total > 0): ?>
<div class="modal fade" id="celebrationsPopupModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg">
<div class="modal-header border-0 pb-0">
<div>
<h5 class="modal-title">Upcoming Celebrations</h5>
<div class="small text-muted mt-1">Next 30 days · <?= number_format($celebrations_total) ?> reminders</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-3">
<div class="list-group list-group-flush">
<?php foreach ($celebrationsPopupItems as $it): ?>
<div class="list-group-item d-flex justify-content-between align-items-start">
<div>
<div class="fw-semibold text-dark"><?= htmlspecialchars($it['name'] ?? '-') ?></div>
<div class="small text-muted"><?= htmlspecialchars($it['type'] ?? 'Celebration') ?> · <?= htmlspecialchars($it['occurs_on'] ?? '-') ?></div>
</div>
<span class="badge bg-success-subtle text-success align-self-center"><?= htmlspecialchars($it['label'] ?? '') ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<a href="#client-celebrations" class="btn btn-success" data-bs-dismiss="modal">View celebrations</a>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Dismiss</button>
</div>
</div>
</div>
</div>
<script>
(function () {
var modalEl = document.getElementById('celebrationsPopupModal');
if (!modalEl || typeof bootstrap === 'undefined') { return; }
var d = new Date();
var dayKey = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
var storageKey = 'ccCelebrationsPopupSeen_<?= (int)($_SESSION['user_id'] ?? 0) ?>_<?= (int)($companyId ?? 0) ?>_' + dayKey;
if (window.localStorage.getItem(storageKey) === 'true') { return; }
var modal = new bootstrap.Modal(modalEl);
modal.show();
modalEl.addEventListener('hidden.bs.modal', function () {
try { window.localStorage.setItem(storageKey, 'true'); } catch (e) {}
}, { once: true });
}());
</script>
<?php endif; ?>
<div class="modal fade" id="celebrationClientModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg">
<div class="modal-header border-0 pb-0">
<div>
<h5 class="modal-title" id="celebrationClientName">Client details</h5>
<div class="small text-muted mt-1" id="celebrationClientEmail">-</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-3">
<div class="row g-3">
<div class="col-12">
<div class="cc-date-detail">
<div class="cc-date-detail-label">Birthday</div>
<div class="cc-date-detail-value" id="celebrationClientBirthday">Not available</div>
</div>
</div>
<div class="col-12">
<div class="cc-date-detail">
<div class="cc-date-detail-label">Anniversary</div>
<div class="cc-date-detail-value" id="celebrationClientAnniversary">Not available</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<template id="workspace-template-leads">
<div class="cc-tab-shell card border-0">
<div class="card-header d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
<div>
<h6 class="mb-1 fw-bold text-navy"><i class="fa-solid fa-address-card me-2"></i>Lead Pipeline</h6>
<div class="small text-muted">Recent leads ready for contact and qualification.</div>
</div>
<div class="d-flex gap-2">
<a href="agent-leads.php" class="btn btn-sm btn-outline-primary rounded-pill px-3">View all leads</a>
<a href="agent-leads.php" class="btn btn-sm btn-primary rounded-pill px-3">Add lead</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table align-middle cc-table">
<thead>
<tr>
<th>Client</th>
<th>Status</th>
<th>Assigned</th>
<th>Created</th>
<th class="text-end">Quick Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($recentLeads)): ?>
<tr><td colspan="5" class="cc-empty-state">No recent leads available.</td></tr>
<?php else: foreach ($recentLeads as $lead): ?>
<tr>
<td class="cc-client-cell">
<strong><?= htmlspecialchars($lead['client_name'] ?? '-') ?></strong>
<span><?= htmlspecialchars($lead['client_phone'] ?? $lead['client_email'] ?? '-') ?></span>
</td>
<td><span class="cc-status-pill cc-status-<?= htmlspecialchars(strtolower((string)($lead['status'] ?? 'new'))) ?>"><?= htmlspecialchars(ucfirst((string)($lead['status'] ?? 'new'))) ?></span></td>
<td class="text-muted small"><?= !empty($lead['agent_id']) ? 'Agent #' . (int)$lead['agent_id'] : 'Unassigned' ?></td>
<td class="text-muted small"><?= !empty($lead['created_at']) ? date('M d, Y g:i A', strtotime($lead['created_at'])) : '-' ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<a href="agent-leads.php" class="btn btn-light">View</a>
<a href="customer-care.php#logs" class="btn btn-outline-primary">Call</a>
<a href="customer-care.php#tasks" class="btn btn-outline-secondary">Follow Up</a>
</div>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</template>
<template id="workspace-template-calls">
<div class="cc-tab-shell card border-0">
<div class="card-header d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
<div>
<h6 class="mb-1 fw-bold text-navy"><i class="fa-solid fa-phone-volume me-2"></i>Call Activity</h6>
<div class="small text-muted">Latest conversations, next follow-ups, and outreach context.</div>
</div>
<div class="d-flex gap-2">
<a href="customer-care.php#logs" class="btn btn-sm btn-outline-primary rounded-pill px-3">Open logs</a>
<a href="customer-care.php#logs" class="btn btn-sm btn-primary rounded-pill px-3">Log call</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table align-middle cc-table">
<thead>
<tr>
<th>Caller</th>
<th>Purpose</th>
<th>Channel</th>
<th>Next Follow-up</th>
<th>Status</th>
<th class="text-end">Quick Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($callLogs)): ?>
<tr><td colspan="6" class="cc-empty-state">No call activity logged yet.</td></tr>
<?php else: foreach ($callLogs as $call): ?>
<tr>
<td class="cc-client-cell">
<strong><?= htmlspecialchars($call['name'] ?? '-') ?></strong>
<span><?= htmlspecialchars($call['phone'] ?? '-') ?></span>
</td>
<td class="text-muted small"><?= htmlspecialchars($call['note'] ?? 'No purpose recorded') ?></td>
<td class="text-muted small"><?= htmlspecialchars($call['via'] ?? 'Direct') ?></td>
<td class="text-muted small"><?= !empty($call['next_contact_at']) ? date('M d, Y g:i A', strtotime($call['next_contact_at'])) : 'Not scheduled' ?></td>
<td><span class="cc-status-pill cc-status-<?= htmlspecialchars(strtolower((string)($call['type'] ?? 'open'))) ?>"><?= htmlspecialchars(ucfirst((string)($call['type'] ?? 'open'))) ?></span></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<a href="customer-care.php#communication" class="btn btn-light">View</a>
<a href="customer-care.php#tasks" class="btn btn-outline-primary">Reschedule</a>
<a href="customer-care.php#logs" class="btn btn-outline-secondary">Log Next</a>
</div>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</template>
<template id="workspace-template-tickets">
<div class="cc-tab-shell card border-0">
<div class="card-header d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
<div>
<h6 class="mb-1 fw-bold text-navy"><i class="fa-solid fa-ticket me-2"></i>Support Tickets</h6>
<div class="small text-muted">Active issues across open, pending, and resolved ticket queues.</div>
</div>
<div class="d-flex gap-2">
<a href="customer-care.php#tickets" class="btn btn-sm btn-outline-primary rounded-pill px-3">Open support board</a>
<button type="button" class="btn btn-sm btn-primary rounded-pill px-3" data-bs-toggle="modal" data-bs-target="#newTicketModal">New ticket</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table align-middle cc-table">
<thead>
<tr>
<th>Ticket</th>
<th>Priority</th>
<th>Status</th>
<th>Client</th>
<th>Created</th>
<th class="text-end">Quick Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($tickets)): ?>
<tr><td colspan="6" class="cc-empty-state">No support tickets match the current queue.</td></tr>
<?php else: foreach (array_slice($tickets, 0, 10) as $ticket): ?>
<tr>
<td class="cc-client-cell">
<strong>#<?= (int)$ticket['id'] ?> · <?= htmlspecialchars($ticket['subject'] ?? 'Ticket') ?></strong>
<span><?= htmlspecialchars(strlen((string)($ticket['message'] ?? '')) > 70 ? substr((string)$ticket['message'], 0, 67) . '...' : ((string)($ticket['message'] ?? 'No ticket message.'))) ?></span>
</td>
<td><span class="cc-status-pill cc-status-<?= htmlspecialchars(strtolower((string)($ticket['priority'] ?? 'medium'))) ?>"><?= htmlspecialchars(ucfirst((string)($ticket['priority'] ?? 'medium'))) ?></span></td>
<td><span class="cc-status-pill cc-status-<?= htmlspecialchars(strtolower((string)($ticket['status'] ?? 'open'))) ?>"><?= htmlspecialchars(ucfirst((string)($ticket['status'] ?? 'open'))) ?></span></td>
<td class="text-muted small"><?= htmlspecialchars($ticket['client_name'] ?? 'Unknown client') ?></td>
<td class="text-muted small"><?= !empty($ticket['created_at']) ? date('M d, Y g:i A', strtotime($ticket['created_at'])) : '-' ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<a href="customer-care.php#tickets" class="btn btn-light">Open</a>
<a href="customer-rep-dashboard.php?assigned_to=me" class="btn btn-outline-primary">My Queue</a>
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#newTicketModal">Add Ticket</button>
</div>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</template>
<div class="modal fade" id="newTicketModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<form class="modal-content" method="POST" enctype="multipart/form-data" action="customer-rep-dashboard.php">
<div class="modal-header bg-navy text-white">
<h5 class="modal-title"><i class="fa-solid fa-plus-circle me-2"></i>Open New Ticket</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="action" value="create_ticket">
<div class="mb-3">
<label class="form-label">Subject</label>
<input type="text" name="subject" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Priority</label>
<select name="priority" class="form-select">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Message</label>
<textarea name="message" class="form-control" rows="5" required></textarea>
</div>
<div class="mb-3">
<label class="form-label">Attachment</label>
<input type="file" name="attachment" class="form-control">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Submit Ticket</button>
</div>
</form>
</div>
</div>
<?php include 'includes/footer.php'; ?>
<script>
(function () {
var tabButtons = document.querySelectorAll('[data-workspace-tab]');
var tabContent = document.getElementById('workspaceTabContent');
var celebrationModal = document.getElementById('celebrationClientModal');
function renderWorkspaceTab(tabName) {
if (!tabContent) {
return;
}
var template = document.getElementById('workspace-template-' + tabName);
if (!template) {
return;
}
tabContent.innerHTML = template.innerHTML;
tabButtons.forEach(function (button) {
button.classList.toggle('active', button.getAttribute('data-workspace-tab') === tabName);
});
}
tabButtons.forEach(function (button) {
button.addEventListener('click', function () {
renderWorkspaceTab(button.getAttribute('data-workspace-tab'));
});
});
if (celebrationModal) {
celebrationModal.addEventListener('show.bs.modal', function (event) {
var trigger = event.relatedTarget;
if (!trigger) {
return;
}
var modalName = celebrationModal.querySelector('#celebrationClientName');
var modalEmail = celebrationModal.querySelector('#celebrationClientEmail');
var modalBirthday = celebrationModal.querySelector('#celebrationClientBirthday');
var modalAnniversary = celebrationModal.querySelector('#celebrationClientAnniversary');
if (modalName) {
modalName.textContent = trigger.getAttribute('data-client-name') || 'Client details';
}
if (modalEmail) {
modalEmail.textContent = trigger.getAttribute('data-client-email') || 'No email available';
}
if (modalBirthday) {
modalBirthday.textContent = trigger.getAttribute('data-client-birthday') || 'Not available';
}
if (modalAnniversary) {
modalAnniversary.textContent = trigger.getAttribute('data-client-anniversary') || 'Not available';
}
});
}
renderWorkspaceTab('leads');
})();
</script>