| 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 'includes/db.php';
require_once 'includes/functions.php';
if (!isset($_SESSION['user_id'])) {
header("Location: index.php");
exit;
}
$role = $_SESSION['user_role'] ?? 'guest';
$roleNorm = strtolower(str_replace([' ', '-'], '_', (string)$role));
$userId = (int)($_SESSION['user_id'] ?? 0);
$isPrivilegedClientView = in_array($roleNorm, ['super_admin','admin','estate_manager','sales_manager','chairman_ceo','executive','finance','finance_officer','finance_manager'], true);
$isMarketingStaff = in_array($roleNorm, ['marketing','marketer','agent','sales_agent','sales'], true);
$isContactStaff = in_array($roleNorm, ['contact_rep','customer_rep','contact_centre','contact_center'], true);
try {
if (function_exists('hasColumn') && !hasColumn($pdo, 'users', 'created_by')) {
try { $pdo->exec("ALTER TABLE users ADD COLUMN created_by INT NULL"); } catch (Throwable $e) {}
}
} catch (Throwable $e) {}
function clients_isOwnedClient(PDO $pdo, int $clientId, int $userId, string $roleNorm, ?int $companyId = null): bool {
if ($clientId <= 0 || $userId <= 0) return false;
if (in_array($roleNorm, ['super_admin','admin','estate_manager','sales_manager','chairman_ceo','executive','finance','finance_officer','finance_manager'], true)) return true;
$companyId = $companyId ? (int)$companyId : 0;
try {
if (function_exists('hasColumn') && hasColumn($pdo,'users','created_by')) {
$q = "SELECT 1 FROM users WHERE id = ? AND role = 'client' AND created_by = ?";
$params = [$clientId, $userId];
if ($companyId && function_exists('hasColumn') && hasColumn($pdo,'users','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyId; }
$st = $pdo->prepare($q);
$st->execute($params);
if ($st->fetchColumn()) return true;
}
} catch (Throwable $e) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) {
$w = "submitted_by_user = ?";
$params = [$userId];
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','marketer_id')) { $w .= " OR marketer_id = ?"; $params[] = $userId; }
$q = "SELECT 1 FROM deals_submit WHERE user_id = ? AND ($w)";
$p2 = array_merge([$clientId], $params);
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals_submit','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $p2[] = $companyId; }
$q .= " LIMIT 1";
$st = $pdo->prepare($q);
$st->execute($p2);
if ($st->fetchColumn()) return true;
}
} catch (Throwable $e) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'payments'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('payments','submitted_by_user')) {
$clientCol = (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) ? 'client_id' : 'user_id';
$q = "SELECT 1 FROM payments WHERE {$clientCol} = ? AND submitted_by_user = ?";
$params = [$clientId, $userId];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyId; }
$q .= " LIMIT 1";
$st = $pdo->prepare($q);
$st->execute($params);
if ($st->fetchColumn()) return true;
}
} catch (Throwable $e) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'deals'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('deals','marketer_id')) {
$q = "SELECT 1 FROM deals WHERE user_id = ? AND marketer_id = ?";
$params = [$clientId, $userId];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyId; }
$q .= " LIMIT 1";
$st = $pdo->prepare($q);
$st->execute($params);
if ($st->fetchColumn()) return true;
}
} catch (Throwable $e) {}
return false;
}
if (!isset($_SESSION['company_id'])) {
$stmt = $pdo->prepare("SELECT company_id FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$_SESSION['company_id'] = $stmt->fetchColumn() ?: null;
}
$companyId = $_SESSION['company_id'] ?? null;
$companyFilterUsers = '';
$companyFilterAllocationsPlain = '';
$companyFilterAllocationsA = '';
$companyFilterAllocationsA2 = '';
$companyFilterAllocationsAA = '';
$companyFilterPaymentsAllocations = '';
if ($companyId) {
$companyId = (int)$companyId;
$companyFilterUsers = " AND u.company_id = {$companyId}";
$companyFilterAllocationsPlain = " AND company_id = {$companyId}";
$companyFilterAllocationsA = " AND a.company_id = {$companyId}";
$companyFilterAllocationsA2 = " AND a2.company_id = {$companyId}";
$companyFilterAllocationsAA = " AND aa.company_id = {$companyId}";
$companyFilterPaymentsAllocations = " AND a.company_id = {$companyId}";
}
// Available properties for quick allocation modal
$propertiesAvail = [];
try {
$qProps = "SELECT id, title FROM properties WHERE status = 'available'" . ($companyId ? " AND company_id = " . (int)$companyId : "") . " ORDER BY title";
$propertiesAvail = $pdo->query($qProps)->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Throwable $e) { $propertiesAvail = []; }
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_client_from_list'])) {
$name = trim($_POST['new_name'] ?? '');
$email = trim($_POST['new_email'] ?? '');
$phone = trim($_POST['new_phone'] ?? '');
$location = trim($_POST['new_location'] ?? '');
$interest = trim($_POST['new_interest'] ?? '');
$plot = trim($_POST['new_plot_sqm'] ?? '');
$referral = trim($_POST['new_referral'] ?? '');
$notes = trim($_POST['new_notes'] ?? '');
$dobRaw = trim($_POST['new_dob'] ?? '');
$annRaw = trim($_POST['new_anniversary'] ?? '');
$goOnboarding = isset($_POST['go_onboarding']) && (string)$_POST['go_onboarding'] === '1';
if ($name !== '' && $email !== '' && $dobRaw !== '') {
try {
$chk = $pdo->prepare("SELECT id FROM users WHERE email = ? LIMIT 1");
$chk->execute([$email]);
if ($chk->fetchColumn()) {
header("Location: clients.php?toast=" . urlencode("Email already exists") . "&type=danger");
exit;
}
$pwd = bin2hex(random_bytes(4));
$hash = password_hash($pwd, PASSWORD_DEFAULT);
$cols = ["name","email","password","role"];
$vals = [$name,$email,$hash,'client'];
if (function_exists('hasColumn') && hasColumn($pdo,'users','company_id') && $companyId) { array_unshift($cols, "company_id"); array_unshift($vals, $companyId); }
if (($isMarketingStaff || $isContactStaff) && function_exists('hasColumn') && hasColumn($pdo,'users','created_by')) { $cols[] = "created_by"; $vals[] = $userId; }
if (function_exists('hasColumn') && hasColumn($pdo,'users','status')) { $cols[] = "status"; $vals[] = "registered"; }
if (function_exists('hasColumn') && hasColumn($pdo,'users','phone') && $phone !== '') { $cols[] = "phone"; $vals[] = $phone; }
if (function_exists('hasColumn') && hasColumn($pdo,'users','location') && $location !== '') { $cols[] = "location"; $vals[] = $location; }
if (function_exists('hasColumn') && hasColumn($pdo,'users','referral_source') && $referral !== '') { $cols[] = "referral_source"; $vals[] = $referral; }
if (function_exists('hasColumn') && hasColumn($pdo,'users','notes')) {
$cols[] = "notes";
$vals[] = trim(($interest !== '' ? "Interest: ".$interest.". " : "") . ($plot !== '' ? "Plot: ".$plot." sqm. " : "") . ($notes !== '' ? $notes : ""));
}
$dob = null;
$ts = strtotime($dobRaw);
if ($ts) { $dob = date('Y-m-d', $ts); }
if (!$dob) {
header("Location: clients.php?toast=" . urlencode("Birthday is required") . "&type=danger");
exit;
}
$ann = null;
if ($annRaw !== '') {
$tsA = strtotime($annRaw);
if ($tsA) { $ann = date('Y-m-d', $tsA); }
}
$colCheck = function($table, $col) use ($pdo) {
if (function_exists('hasColumn')) return hasColumn($pdo, $table, $col);
if (function_exists('tableHasColumn')) return tableHasColumn($table, $col);
return false;
};
if (($dob || $ann) && !$colCheck('users','date_of_birth')) {
try { $pdo->exec("ALTER TABLE users ADD COLUMN date_of_birth DATE NULL"); } catch (Throwable $e) {}
}
if (($dob || $ann) && !$colCheck('users','anniversary_date')) {
try { $pdo->exec("ALTER TABLE users ADD COLUMN anniversary_date DATE NULL"); } catch (Throwable $e) {}
}
if ($dob && $colCheck('users','date_of_birth')) { $cols[] = "date_of_birth"; $vals[] = $dob; }
if ($ann && $colCheck('users','anniversary_date')) { $cols[] = "anniversary_date"; $vals[] = $ann; }
$sql = "INSERT INTO users (" . implode(',', $cols) . ") VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
$ins = $pdo->prepare($sql);
$ins->execute($vals);
$newClientId = (int)$pdo->lastInsertId();
if ($goOnboarding && $newClientId > 0) {
header("Location: client-onboarding.php?client_id=" . (int)$newClientId . "&toast=" . urlencode("Client created. Complete onboarding now.") . "&type=success");
exit;
}
header("Location: clients.php?toast=" . urlencode("Client created") . "&type=success");
exit;
} catch (Throwable $e) {
header("Location: clients.php?toast=" . urlencode("Failed to create client") . "&type=danger");
exit;
}
} else {
header("Location: clients.php?toast=" . urlencode("Please fill required fields (Birthday is required)") . "&type=danger");
exit;
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['seed_demo_clients'])) {
$demo = [
['name' => 'Adaobi Okafor', 'email' => 'adaobi.okafor.demo@aiben.test', 'phone' => '0802-111-2222', 'location' => 'Lagos'],
['name' => 'Ibrahim Musa', 'email' => 'ibrahim.musa.demo@aiben.test', 'phone' => '0803-333-4444', 'location' => 'Abuja'],
['name' => 'Chidinma Uche', 'email' => 'chidinma.uche.demo@aiben.test', 'phone' => '0701-555-6666', 'location' => 'Port Harcourt'],
['name' => 'Tunde Adebayo', 'email' => 'tunde.adebayo.demo@aiben.test', 'phone' => '0814-777-8888', 'location' => 'Ibadan'],
['name' => 'Grace Nwankwo', 'email' => 'grace.nwankwo.demo@aiben.test', 'phone' => '0906-999-0000', 'location' => 'Enugu'],
];
try {
foreach ($demo as $d) {
$chk = $pdo->prepare("SELECT id FROM users WHERE email = ? LIMIT 1");
$chk->execute([$d['email']]);
if ($chk->fetchColumn()) { continue; }
$pwd = bin2hex(random_bytes(4));
$hash = password_hash($pwd, PASSWORD_DEFAULT);
$cols = ["name","email","password","role"];
$vals = [$d['name'],$d['email'],$hash,'client'];
if (function_exists('hasColumn') && hasColumn($pdo,'users','company_id') && $companyId) { array_unshift($cols, "company_id"); array_unshift($vals, (int)$companyId); }
if (function_exists('hasColumn') && hasColumn($pdo,'users','status')) { $cols[] = "status"; $vals[] = "registered"; }
if (function_exists('hasColumn') && hasColumn($pdo,'users','phone') && !empty($d['phone'])) { $cols[] = "phone"; $vals[] = $d['phone']; }
if (function_exists('hasColumn') && hasColumn($pdo,'users','location') && !empty($d['location'])) { $cols[] = "location"; $vals[] = $d['location']; }
$sql = "INSERT INTO users (" . implode(',', $cols) . ") VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
$ins = $pdo->prepare($sql);
$ins->execute($vals);
}
header("Location: clients.php?toast=" . urlencode("Demo clients added") . "&type=success");
exit;
} catch (Throwable $e) {
header("Location: clients.php?toast=" . urlencode("Failed to add demo clients") . "&type=danger");
exit;
}
}
function cd_safe_upload($file){
try{
if(!$file || ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) return '';
$dir = __DIR__ . '/uploads/receipts';
if(!is_dir($dir)) { @mkdir($dir, 0777, true); }
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$name = 'receipt_' . date('Ymd_His') . '_' . mt_rand(1000,9999) . '.' . $ext;
$dest = $dir . '/' . $name;
if(move_uploaded_file($file['tmp_name'], $dest)) {
return 'uploads/receipts/' . $name;
}
}catch(Throwable $e){}
return '';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_deal_from_client'])) {
$clientId = isset($_POST['deal_client_id']) && ctype_digit($_POST['deal_client_id']) ? (int)$_POST['deal_client_id'] : 0;
if (($isMarketingStaff || $isContactStaff) && !$isPrivilegedClientView) {
if (!clients_isOwnedClient($pdo, $clientId, $userId, $roleNorm, $companyId ? (int)$companyId : null)) {
header("Location: clients.php?toast=" . urlencode("You can only submit for your own clients") . "&type=danger");
exit;
}
}
$clientName = trim($_POST['deal_client_name'] ?? '');
$estate = trim($_POST['property_estate'] ?? '');
$projectDesc = trim($_POST['project_desc'] ?? '');
$amountOffered = isset($_POST['amount_offered']) ? (float)$_POST['amount_offered'] : 0;
$amountPaidNow = isset($_POST['amount_paid_now']) ? (float)$_POST['amount_paid_now'] : 0;
$discountAmount = isset($_POST['discount_amount']) ? (float)$_POST['discount_amount'] : 0;
$commissionPct = isset($_POST['commission_pct']) ? (float)$_POST['commission_pct'] : 0;
$marketerComm = isset($_POST['marketer_comm']) ? (float)$_POST['marketer_comm'] : 0;
$agentComm = isset($_POST['agent_comm']) ? (float)$_POST['agent_comm'] : 0;
$receiptPath = cd_safe_upload($_FILES['receipt_file'] ?? null);
$txnDates = $_POST['txn_date'] ?? [];
$txnAmounts = $_POST['txn_amount'] ?? [];
$txns = [];
if (is_array($txnDates) && is_array($txnAmounts)) {
$n = min(count($txnDates), count($txnAmounts));
for ($i=0; $i<$n; $i++) {
$d = trim($txnDates[$i]);
$a = (float)$txnAmounts[$i];
if ($d !== '' && $a > 0) $txns[] = ['date' => $d, 'amount' => $a];
}
}
$meta = [
'client_id' => $clientId,
'client_name' => $clientName,
'property_estate' => $estate,
'project_desc' => $projectDesc,
'amount_offered' => $amountOffered,
'discount_amount' => $discountAmount,
'commission_pct' => $commissionPct,
'marketer_comm' => $marketerComm,
'agent_comm' => $agentComm,
'transactions' => $txns,
'submitted_by_role' => $role,
'submitted_by_user' => $_SESSION['user_id'] ?? null,
];
try {
$dealSql = "INSERT INTO deals (user_id, property_estate, project_desc, amount_offered, amount_paid_now, discount_amount, receipt_file, commission_pct, marketer_comm, agent_comm, status, company_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending_verification', ?, NOW())";
$dealVals = [$clientId, $estate, $projectDesc, $amountOffered, $amountPaidNow, $discountAmount, $receiptPath ?: null, $commissionPct, $marketerComm, $agentComm, $companyId];
$ok = false; $dealId = 0;
try {
$ins = $pdo->prepare($dealSql);
$ins->execute($dealVals);
$dealId = (int)$pdo->lastInsertId();
$ok = true;
} catch (Throwable $te) {
$ok = false;
}
if (!$ok) {
$fields = []; $places = []; $vals = [];
if (function_exists('tableHasColumn') && tableHasColumn('payments','amount')) { $fields[]='amount'; $places[]='?'; $vals[]=$amountPaidNow; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','status')) { $fields[]='status'; $places[]='?'; $vals[]='pending_verification'; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','reference')) { $fields[]='reference'; $places[]='?'; $vals[]='deal-from-client'; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','proof_file')) { $fields[]='proof_file'; $places[]='?'; $vals[]=$receiptPath ?: null; }
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) { $fields[]='company_id'; $places[]='?'; $vals[]=$companyId; }
if ($clientId && function_exists('tableHasColumn') && tableHasColumn('payments','user_id')) { $fields[]='user_id'; $places[]='?'; $vals[]=$clientId; }
if ($dealId > 0 && function_exists('tableHasColumn') && tableHasColumn('payments','deal_id')) { $fields[]='deal_id'; $places[]='?'; $vals[]=$dealId; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','created_at')) { $fields[]='created_at'; $places[]='NOW()'; }
if ($fields) {
$sql = "INSERT INTO payments (" . implode(',', $fields) . ") VALUES (" . implode(',', $places) . ")";
$ins = $pdo->prepare($sql);
$ins->execute($vals);
$pid = (int)$pdo->lastInsertId();
if (function_exists('tableHasColumn') && tableHasColumn('payments','meta_json')) {
$up = $pdo->prepare("UPDATE payments SET meta_json = ? WHERE id = ?");
$up->execute([json_encode($meta), $pid]);
}
} else {
header("Location: clients.php?toast=" . urlencode("No supported schema for submission") . "&type=danger");
exit;
}
} else {
try {
$up = $pdo->prepare("UPDATE deals SET meta_json = ? WHERE id = ?");
$up->execute([json_encode($meta), $dealId]);
} catch (Throwable $ue) {}
try {
$pf = []; $pp = []; $pv = [];
if (function_exists('tableHasColumn') && tableHasColumn('payments','amount')) { $pf[]='amount'; $pp[]='?'; $pv[]=$amountPaidNow; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','status')) { $pf[]='status'; $pp[]='?'; $pv[]='pending_verification'; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','proof_file')) { $pf[]='proof_file'; $pp[]='?'; $pv[]=$receiptPath ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','company_id') && $companyId) { $pf[]='company_id'; $pp[]='?'; $pv[]=$companyId; }
if ($clientId && function_exists('tableHasColumn') && tableHasColumn('payments','user_id')) { $pf[]='user_id'; $pp[]='?'; $pv[]=$clientId; }
if ($dealId > 0 && function_exists('tableHasColumn') && tableHasColumn('payments','deal_id')) { $pf[]='deal_id'; $pp[]='?'; $pv[]=$dealId; }
if (function_exists('tableHasColumn') && tableHasColumn('payments','created_at')) { $pf[]='created_at'; $pp[]='NOW()'; }
if ($pf) {
$sql = "INSERT INTO payments (" . implode(',', $pf) . ") VALUES (" . implode(',', $pp) . ")";
$insP = $pdo->prepare($sql);
$insP->execute($pv);
$pid = (int)$pdo->lastInsertId();
if (function_exists('tableHasColumn') && tableHasColumn('payments','meta_json')) {
$meta['deal_id'] = $dealId;
$upP = $pdo->prepare("UPDATE payments SET meta_json = ? WHERE id = ?");
$upP->execute([json_encode($meta), $pid]);
}
}
} catch (Throwable $pe) {}
}
header("Location: clients.php?toast=" . urlencode("Deal submitted for verification"));
exit;
} catch (Throwable $e) {
header("Location: clients.php?toast=" . urlencode("Failed to submit deal") . "&type=danger");
exit;
}
}
try {
$scopeWhere = "u.role = 'client'" . $companyFilterUsers;
$scopeParams = [];
if (($isMarketingStaff || $isContactStaff) && !$isPrivilegedClientView && $userId > 0) {
$sc = [];
if (function_exists('hasColumn') && hasColumn($pdo,'users','created_by')) { $sc[] = "u.created_by = ?"; $scopeParams[] = $userId; }
try {
if ($pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) {
$w = "ds.submitted_by_user = ?";
$scopeParams[] = $userId;
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','marketer_id')) { $w .= " OR ds.marketer_id = ?"; $scopeParams[] = $userId; }
$sc[] = "EXISTS (SELECT 1 FROM deals_submit ds WHERE ds.user_id = u.id AND ($w)" . ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals_submit','company_id') ? " AND (ds.company_id = " . (int)$companyId . " OR ds.company_id IS NULL)" : "") . ")";
}
} catch (Throwable $e) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'payments'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('payments','submitted_by_user')) {
$clientCol = (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) ? 'pp.client_id' : 'pp.user_id';
$sc[] = "EXISTS (SELECT 1 FROM payments pp WHERE {$clientCol} = u.id AND pp.submitted_by_user = ?" . ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id') ? " AND (pp.company_id = " . (int)$companyId . " OR pp.company_id IS NULL)" : "") . ")";
$scopeParams[] = $userId;
}
} catch (Throwable $e) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'deals'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('deals','marketer_id')) {
$sc[] = "EXISTS (SELECT 1 FROM deals d WHERE d.user_id = u.id AND d.marketer_id = ?" . ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals','company_id') ? " AND (d.company_id = " . (int)$companyId . " OR d.company_id IS NULL)" : "") . ")";
$scopeParams[] = $userId;
}
} catch (Throwable $e) {}
if (!empty($sc)) {
$scopeWhere .= " AND (" . implode(" OR ", $sc) . ")";
} else {
$scopeWhere .= " AND 1=0";
}
}
$st = $pdo->prepare("SELECT COUNT(*) FROM users u WHERE {$scopeWhere}");
$st->execute($scopeParams);
$total_clients = (int)($st->fetchColumn() ?: 0);
$st = $pdo->prepare("SELECT COUNT(*) FROM users u WHERE {$scopeWhere} AND MONTH(u.created_at) = MONTH(CURRENT_DATE()) AND YEAR(u.created_at) = YEAR(CURRENT_DATE())");
$st->execute($scopeParams);
$new_clients = (int)($st->fetchColumn() ?: 0);
$active_clients = $pdo->query("SELECT COUNT(DISTINCT user_id) FROM allocations WHERE status IN ('approved', 'reserved', 'completed', 'allocated', 'finalized', 'active')" . $companyFilterAllocationsPlain)->fetchColumn();
$stmt = $pdo->query("
SELECT SUM(p.price)
FROM allocations a
JOIN properties p ON a.property_id = p.id
WHERE a.status IN ('approved', 'completed', 'allocated', 'finalized', 'active')" . $companyFilterPaymentsAllocations);
$portfolio_value = $stmt->fetchColumn() ?: 0;
} catch (Exception $e) {
$total_clients = 0;
$new_clients = 0;
$active_clients = 0;
$portfolio_value = 0;
}
// 2. Main List Query
$search_query = $_GET['search'] ?? '';
$view_filter = $_GET['view'] ?? 'all'; // all, active, leads, overdue, due_today, due_7d
$dealDueInfo = function_exists('kpiDealPaymentDueSummary') ? kpiDealPaymentDueSummary($pdo, $companyId) : [];
$overdueUids = $dealDueInfo['overdue_user_ids'] ?? [];
$dueTodayUids = $dealDueInfo['due_today_user_ids'] ?? [];
$due7dUids = $dealDueInfo['due_7_user_ids'] ?? [];
// optional phone column
$phoneSel = (function_exists('hasColumn') && hasColumn($pdo,'users','phone')) ? "u.phone" : "NULL";
$avatarSel = "NULL";
if (function_exists('hasColumn')) {
if (hasColumn($pdo,'users','avatar')) { $avatarSel = "u.avatar"; }
elseif (hasColumn($pdo,'users','profile_photo')) { $avatarSel = "u.profile_photo"; }
elseif (hasColumn($pdo,'users','photo')) { $avatarSel = "u.photo"; }
elseif (hasColumn($pdo,'users','picture')) { $avatarSel = "u.picture"; }
elseif (hasColumn($pdo,'users','image_path')) { $avatarSel = "u.image_path"; }
}
$sql = "
SELECT u.id, u.name, u.email, {$phoneSel} AS phone, u.created_at, {$avatarSel} AS avatar_url,
(SELECT COUNT(*) FROM allocations a2 WHERE a2.user_id = u.id" . $companyFilterAllocationsA2 . ") as property_count,
(SELECT COALESCE(SUM(p.amount), 0) FROM payments p JOIN allocations a ON p.allocation_id = a.id WHERE a.user_id = u.id AND p.status = 'approved'" . $companyFilterPaymentsAllocations . ") as total_paid
,
(SELECT COALESCE(SUM(pp.price), 0) FROM allocations aa JOIN properties pp ON aa.property_id = pp.id WHERE aa.user_id = u.id" . $companyFilterAllocationsAA . ") as property_total
FROM users u
WHERE u.role = 'client'" . $companyFilterUsers . "
";
$params = [];
if (($isMarketingStaff || $isContactStaff) && !$isPrivilegedClientView && $userId > 0) {
$sc = [];
if (function_exists('hasColumn') && hasColumn($pdo,'users','created_by')) { $sc[] = "u.created_by = ?"; $params[] = $userId; }
try {
if ($pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) {
$w = "ds.submitted_by_user = ?";
$params[] = $userId;
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','marketer_id')) { $w .= " OR ds.marketer_id = ?"; $params[] = $userId; }
$sc[] = "EXISTS (SELECT 1 FROM deals_submit ds WHERE ds.user_id = u.id AND ($w)" . ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals_submit','company_id') ? " AND (ds.company_id = " . (int)$companyId . " OR ds.company_id IS NULL)" : "") . ")";
}
} catch (Throwable $e) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'payments'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('payments','submitted_by_user')) {
$clientCol = (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) ? 'pp.client_id' : 'pp.user_id';
$sc[] = "EXISTS (SELECT 1 FROM payments pp WHERE {$clientCol} = u.id AND pp.submitted_by_user = ?" . ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id') ? " AND (pp.company_id = " . (int)$companyId . " OR pp.company_id IS NULL)" : "") . ")";
$params[] = $userId;
}
} catch (Throwable $e) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'deals'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('deals','marketer_id')) {
$sc[] = "EXISTS (SELECT 1 FROM deals d WHERE d.user_id = u.id AND d.marketer_id = ?" . ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals','company_id') ? " AND (d.company_id = " . (int)$companyId . " OR d.company_id IS NULL)" : "") . ")";
$params[] = $userId;
}
} catch (Throwable $e) {}
if (!empty($sc)) {
$sql .= " AND (" . implode(" OR ", $sc) . ")";
} else {
$sql .= " AND 1=0";
}
}
if (!empty($search_query)) {
$sql .= " AND (u.name LIKE ? OR u.email LIKE ?)";
$term = "%$search_query%";
$params[] = $term; $params[] = $term;
}
// Simple filter logic (mocking 'active' based on property count)
if ($view_filter === 'active') {
$sql .= " HAVING property_count > 0";
} elseif ($view_filter === 'leads') {
$sql .= " HAVING property_count = 0";
} elseif ($view_filter === 'overdue' && !empty($overdueUids)) {
$placeholders = implode(',', array_fill(0, count($overdueUids), '?'));
$sql .= " AND u.id IN ($placeholders)";
foreach ($overdueUids as $uid) $params[] = (int)$uid;
} elseif ($view_filter === 'due_today' && !empty($dueTodayUids)) {
$placeholders = implode(',', array_fill(0, count($dueTodayUids), '?'));
$sql .= " AND u.id IN ($placeholders)";
foreach ($dueTodayUids as $uid) $params[] = (int)$uid;
} elseif ($view_filter === 'due_7d' && !empty($due7dUids)) {
$placeholders = implode(',', array_fill(0, count($due7dUids), '?'));
$sql .= " AND u.id IN ($placeholders)";
foreach ($due7dUids as $uid) $params[] = (int)$uid;
} elseif ($view_filter !== 'all') {
// If filter requested but no IDs found, force empty result
$sql .= " AND 1=0";
}
$sql .= " ORDER BY u.created_at DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$clients = $stmt->fetchAll(PDO::FETCH_ASSOC);
try {
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? str_replace('\\','/', realpath($_SERVER['DOCUMENT_ROOT'])) : null;
$appRoot = str_replace('\\','/', realpath(__DIR__));
$relPath = '/';
if ($docRoot && $appRoot && (strpos($appRoot, $docRoot) === 0)) {
$relPath = substr($appRoot, strlen($docRoot));
if ($relPath === false) { $relPath = '/'; }
$relPath = '/' . ltrim($relPath, '/');
}
$absBaseUrl = rtrim($scheme . '://' . $host . rtrim($relPath, '/'), '/') . '/';
} catch (Throwable $e) { $absBaseUrl = '/'; }
function norm_url($u, $base) {
$u = trim((string)$u);
if ($u === '') return '';
if (preg_match('/^https?:\/\//i', $u)) return $u;
if ($u[0] === '/') return rtrim($base, '/') . ltrim($u, '/');
return $u;
}
foreach ($clients as $i => $row) {
$av = trim((string)($row['avatar_url'] ?? ''));
if ($av === '') {
try {
$st = $pdo->prepare("SELECT form_data FROM client_forms WHERE client_id = ? ORDER BY updated_at DESC, created_at DESC LIMIT 1");
$st->execute([(int)$row['id']]);
$fr = $st->fetch(PDO::FETCH_ASSOC);
if ($fr && !empty($fr['form_data'])) {
$data = json_decode($fr['form_data'], true);
if (is_array($data) && !empty($data['passport_photo_path'])) {
$av = (string)$data['passport_photo_path'];
}
}
} catch (Throwable $e) {}
}
if ($av !== '') {
$clients[$i]['avatar_url'] = norm_url($av, $absBaseUrl);
}
}
require 'includes/header.php';
?>
<style>
:root {
--aiben-navy: #001F3F;
--aiben-green: #2ECC40;
--aiben-light-grey: #F8F9FA;
}
body {
background-color: #f4f6f9;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
#addClientModal .modal-dialog-scrollable .modal-content{
max-height: calc(100vh - 2rem) !important;
display: flex !important;
flex-direction: column !important;
}
#addClientModal .modal-dialog-scrollable .modal-body{
overflow-y: auto !important;
overflow-x: hidden;
max-height: calc(100vh - 230px);
}
#addClientModal textarea.form-control{
min-height: 84px;
}
#addClientModal .modal-footer{
margin-top: auto;
}
/* KPI Cards */
.kpi-card {
background: white;
border: 1px solid rgba(0,0,0,0.05);
border-radius: 12px;
padding: 1.25rem;
transition: transform 0.2s, box-shadow 0.2s;
height: 100%;
}
.kpi-card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(0,0,0,0.05);
}
.kpi-icon {
width: 48px; height: 48px;
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 1.25rem;
margin-bottom: 1rem;
}
.kpi-card.kpi-glass {
background: linear-gradient(180deg, rgba(14,165,233,0.06), rgba(255,255,255,0.95));
border: 1px solid rgba(226,232,240,0.7);
box-shadow: 0 16px 28px rgba(14,31,63,0.08);
}
.kpi-icon-glow {
background: radial-gradient(closest-side, rgba(14,165,233,0.15), rgba(14,165,233,0.02));
border: 1px solid rgba(226,232,240,0.7);
box-shadow: inset 0 0 12px rgba(14,165,233,0.18), 0 8px 16px rgba(14,31,63,0.08);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.kpi-icon-glow:hover {
transform: translateY(-1px);
box-shadow: inset 0 0 14px rgba(14,165,233,0.22), 0 12px 20px rgba(14,31,63,0.12);
}
/* Table & Rows */
.client-table thead th {
background-color: #F8F9FA;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.5px;
border-bottom: 2px solid #E9ECEF;
padding: 1rem;
}
.client-table tbody td {
vertical-align: middle;
padding: 1rem;
border-bottom: 1px solid #E9ECEF;
font-size: 0.9rem;
}
.clients-table-scroll{
overflow:auto !important;
overflow-x:auto !important;
overflow-y:hidden;
-webkit-overflow-scrolling:touch;
touch-action:pan-x pan-y;
max-width:100%;
cursor:grab;
}
.clients-table-scroll.clients-grabbing{cursor:grabbing}
.client-table{width:max-content; min-width:1120px}
.client-table th,.client-table td{white-space:nowrap}
.client-table td:first-child{white-space:normal; min-width:320px}
.action-cell {
white-space: nowrap;
min-width: 160px;
position: sticky;
right: 0;
background: inherit;
z-index: 10;
box-shadow: -10px 0 10px -10px rgba(0,0,0,0.1);
}
.client-table thead th.action-cell {
background: #F8F9FA;
}
.client-row:hover .action-cell {
background: #fcfcfc;
}
@media (max-width: 992px) {
.hide-mobile { display: table-cell; }
.action-cell {
position: static;
box-shadow: none;
min-width: auto;
}
.client-table thead th{padding:.7rem .75rem; font-size:.7rem}
.client-table tbody td{padding:.7rem .75rem; font-size:.85rem}
}
.client-row:hover {
background-color: #fcfcfc;
cursor: pointer;
}
/* Avatar */
.avatar-circle {
width: 40px; height: 40px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-weight: bold;
font-size: 0.9rem;
}
.avatar-img {
width: 40px; height: 40px;
border-radius: 50%;
object-fit: cover;
border: 1px solid rgba(255,255,255,0.6);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
background: #ffffff;
}
/* Side Drawer */
.side-drawer-backdrop {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.3);
z-index: 1040;
display: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.side-drawer-backdrop.show { display: block; opacity: 1; }
.side-drawer {
position: fixed; top: 0; right: -600px;
width: 500px; height: 100vh;
background: white;
z-index: 1050;
box-shadow: -5px 0 30px rgba(0,0,0,0.1);
transition: right 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
overflow-y: auto;
display: flex; flex-direction: column;
}
.side-drawer.open { right: 0; }
@media (max-width: 768px) {
.side-drawer { width: 100%; right: -100%; }
}
</style>
<div class="container-fluid px-4 pb-5">
<!-- HEADER -->
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center py-4 gap-3 sticky-top bg-light-grey" style="z-index: 1000; background-color: #f4f6f9;">
<div>
<h2 class="fw-bold text-navy mb-1">Client Management</h2>
<p class="text-muted mb-0 small">Manage client profiles, portfolios, and history</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-secondary bg-white"><i class="fa-solid fa-download me-2"></i>Export CSV</button>
<button class="btn btn-primary btn-primary-custom bg-navy border-0" data-bs-toggle="modal" data-bs-target="#addClientModal"><i class="fa-solid fa-user-plus me-2"></i>Add Client</button>
</div>
</div>
<!-- KPI STRIP -->
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="kpi-card kpi-glass">
<div class="kpi-icon kpi-icon-glow bg-navy bg-opacity-10 text-navy"><i class="fa-solid fa-users"></i></div>
<h3 class="fw-bold mb-0"><?= number_format($total_clients) ?></h3>
<span class="text-muted small">Total Clients</span>
</div>
</div>
<div class="col-6 col-md-3">
<div class="kpi-card kpi-glass">
<div class="kpi-icon kpi-icon-glow bg-success bg-opacity-10 text-success"><i class="fa-solid fa-user-check"></i></div>
<h3 class="fw-bold mb-0"><?= number_format($active_clients) ?></h3>
<span class="text-muted small">Active Clients</span>
</div>
</div>
<div class="col-6 col-md-3">
<div class="kpi-card kpi-glass">
<div class="kpi-icon kpi-icon-glow bg-info bg-opacity-10 text-info"><i class="fa-solid fa-user-plus"></i></div>
<h3 class="fw-bold mb-0"><?= number_format($new_clients) ?></h3>
<span class="text-muted small">New This Month</span>
</div>
</div>
<div class="col-6 col-md-3">
<div class="kpi-card kpi-glass">
<div class="kpi-icon kpi-icon-glow bg-warning bg-opacity-10 text-warning"><i class="fa-solid fa-sack-dollar"></i></div>
<h3 class="fw-bold mb-0">₦<?= number_format($portfolio_value / 1000000, 1) ?>M</h3>
<span class="text-muted small">Total Portfolio Value</span>
</div>
</div>
</div>
<!-- MAIN TABLE CARD -->
<div class="card glass-card rounded-3">
<!-- Toolbar -->
<div class="card-header bg-white py-3 border-bottom">
<div class="row g-3 align-items-center">
<div class="col-md-5">
<form action="" method="GET">
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="fa-solid fa-search text-muted"></i></span>
<input type="text" name="search" class="form-control border-start-0 bg-light" placeholder="Search name or email..." value="<?= htmlspecialchars($search_query) ?>">
</div>
</form>
</div>
<div class="col-md-7">
<div class="d-flex justify-content-md-end gap-2 flex-wrap">
<a href="clients.php?view=all" class="btn btn-sm <?= $view_filter == 'all' ? 'btn-dark' : 'btn-light' ?> rounded-pill px-3">All Clients</a>
<a href="clients.php?view=active" class="btn btn-sm <?= $view_filter == 'active' ? 'btn-success' : 'btn-light' ?> rounded-pill px-3">Active Clients</a>
<a href="clients.php?view=overdue" class="btn btn-sm <?= $view_filter == 'overdue' ? 'btn-danger' : 'btn-light' ?> rounded-pill px-3">Overdue (<?= count($overdueUids) ?>)</a>
<a href="clients.php?view=due_today" class="btn btn-sm <?= $view_filter == 'due_today' ? 'btn-warning' : 'btn-light' ?> rounded-pill px-3">Due Today (<?= count($dueTodayUids) ?>)</a>
<a href="clients.php?view=leads" class="btn btn-sm <?= $view_filter == 'leads' ? 'btn-info' : 'btn-light' ?> rounded-pill px-3">Leads</a>
</div>
</div>
</div>
</div>
<div class="d-sm-none px-3 py-2 small text-muted border-bottom">Swipe left/right to see all columns.</div>
<div class="table-responsive clients-table-scroll">
<table class="table client-table mb-0">
<thead>
<tr>
<th class="ps-4">Client Name</th>
<th class="hide-mobile">Contact Info</th>
<th>Allocations</th>
<th class="hide-mobile">Account Status</th>
<th>Total Paid</th>
<th class="hide-mobile">Joined Date</th>
<th class="text-end pe-4 action-cell">Action</th>
</tr>
</thead>
<tbody>
<?php if (count($clients) > 0): ?>
<?php foreach ($clients as $row): ?>
<tr class="client-row" onclick="openClientDetails(<?= $row['id'] ?>)">
<td class="ps-4">
<div class="d-flex align-items-center">
<?php $av = trim((string)($row['avatar_url'] ?? '')); ?>
<?php if ($av !== ''): ?>
<img src="<?= htmlspecialchars($av) ?>" alt="<?= htmlspecialchars($row['name']) ?>" class="avatar-img me-3">
<?php else: ?>
<div class="avatar-circle bg-light text-dark me-3"><?= strtoupper(substr($row['name'], 0, 1)) ?></div>
<?php endif; ?>
<div>
<div class="fw-bold text-dark d-flex align-items-center gap-2">
<span><?= htmlspecialchars($row['name']) ?></span>
<?php if (in_array($row['id'], $overdueUids)): ?>
<span class="badge bg-danger animate__animated animate__flash animate__infinite"><i class="fa-solid fa-triangle-exclamation"></i> OVERDUE</span>
<?php elseif (in_array($row['id'], $dueTodayUids)): ?>
<span class="badge bg-warning text-dark"><i class="fa-solid fa-calendar-day"></i> DUE TODAY</span>
<?php endif; ?>
<?php if ($row['property_count'] > 0): ?>
<span class="chip chip-success"><i class="fa-solid fa-bolt"></i> Active</span>
<?php else: ?>
<span class="chip chip-muted"><i class="fa-regular fa-lemon"></i> Lead</span>
<?php endif; ?>
</div>
<small class="text-muted">ID: #<?= str_pad($row['id'], 5, '0', STR_PAD_LEFT) ?></small>
<div class="d-md-none mt-1">
<span class="text-muted small"><i class="fa-regular fa-envelope me-1"></i><?= htmlspecialchars($row['email']) ?></span>
</div>
</div>
</div>
</td>
<td class="hide-mobile">
<div class="d-flex flex-column">
<span class="text-dark small"><i class="fa-regular fa-envelope me-2 text-muted"></i><?= htmlspecialchars($row['email']) ?></span>
<!-- Placeholder phone since it's missing in DB -->
<span class="text-muted small mt-1"><i class="fa-solid fa-phone me-2 text-muted"></i>N/A</span>
</div>
</td>
<td>
<?php if ($row['property_count'] > 0): ?>
<span class="pill"><i class="fa-solid fa-layer-group"></i> <?= (int)$row['property_count'] === 1 ? '1 Allocation' : (int)$row['property_count'].' Allocations' ?></span>
<?php else: ?>
<span class="status-badge status-badge-reserved"><i class="fa-solid fa-bookmark"></i> No Allocations</span>
<?php endif; ?>
</td>
<td class="hide-mobile">
<?php
$accStatus = 'Lead'; $badgeCls = 'bg-warning text-dark';
if ((int)$row['property_count'] > 0) { $accStatus = 'Investor'; $badgeCls = 'bg-success'; }
else {
$createdTs = strtotime($row['created_at']);
if ($createdTs && $createdTs < strtotime('-12 months')) { $accStatus = 'Inactive'; $badgeCls = 'bg-secondary'; }
else { $accStatus = 'Registered'; $badgeCls = 'bg-info'; }
}
?>
<span class="badge <?= $badgeCls ?>"><?= $accStatus ?></span>
</td>
<td class="fw-bold text-navy">
<?php
$percentPaid = ($row['property_total'] > 0) ? ($row['total_paid'] / $row['property_total']) * 100 : 0;
?>
<div class="d-flex flex-column">
<span class="pill pill-navy"><i class="fa-solid fa-sack-dollar me-1"></i> ₦<?= number_format((float)$row['total_paid']) ?></span>
<div class="text-muted small mt-1">
<?= ($row['total_paid'] > 0) ? (number_format($percentPaid).'% paid') : 'No payments yet' ?>
</div>
</div>
</td>
<td class="text-muted small hide-mobile">
<?= date('M d, Y', strtotime($row['created_at'])) ?>
</td>
<td class="text-end pe-4 action-cell" onclick="event.stopPropagation()">
<div class="d-flex justify-content-end gap-1">
<a class="btn btn-sm btn-outline-primary" title="View Profile" href="client-profile.php?client_id=<?= (int)$row['id'] ?>"><i class="fa-solid fa-user"></i></a>
<button class="btn btn-sm btn-outline-success" title="Create Deal" data-bs-toggle="modal" data-bs-target="#createDealModal" onclick="setDealClient(<?= (int)$row['id'] ?>,'<?= htmlspecialchars($row['name'], ENT_QUOTES) ?>')"><i class="fa-solid fa-file-invoice-dollar"></i></button>
<button class="btn btn-sm btn-outline-warning" title="Allocate Plot" data-bs-toggle="modal" data-bs-target="#quickAllocateModal" onclick="setAllocateClient(<?= (int)$row['id'] ?>,'<?= htmlspecialchars($row['name'], ENT_QUOTES) ?>')"><i class="fa-solid fa-map-pin"></i></button>
<?php
$dealIdQuick = 0;
try {
$sqlD = "SELECT id FROM deals_submit WHERE user_id = ? ORDER BY created_at DESC LIMIT 1";
$paramsD = [(int)$row['id']];
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','company_id') && !empty($_SESSION['company_id'])) {
$sqlD = "SELECT id FROM deals_submit WHERE user_id = ? AND company_id = ? ORDER BY created_at DESC LIMIT 1";
$paramsD[] = (int)$_SESSION['company_id'];
}
$stD = $pdo->prepare($sqlD);
$stD->execute($paramsD);
$dealIdQuick = (int)($stD->fetchColumn() ?: 0);
} catch (Throwable $e) {}
?>
<?php if ($dealIdQuick > 0): ?>
<a href="ledger.php?deal_id=<?= $dealIdQuick ?>" class="btn btn-sm btn-outline-dark">Ledger</a>
<?php endif; ?>
<?php if (in_array($row['id'], $overdueUids) || in_array($row['id'], $dueTodayUids)): ?>
<button class="btn btn-sm btn-danger" title="Send Payment Reminder" onclick="sendPaymentReminder(<?= (int)$row['id'] ?>)"><i class="fa-solid fa-paper-plane"></i></button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="p-0">
<?= renderEmptyState('fa-solid fa-users-slash', 'No Clients Found', 'Try adjusting your search or filters.', 'user-add.php?role=client', 'Add New Client') ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="card-footer bg-white py-3 border-top">
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">Showing <?= count($clients) ?> entries</small>
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
<li class="page-item active"><a class="page-link bg-navy border-navy" href="#">1</a></li>
<li class="page-item disabled"><a class="page-link" href="#">Next</a></li>
</ul>
</nav>
</div>
</div>
</div>
</div>
<script>
(function(){
var el = document.querySelector('.clients-table-scroll');
if (!el) return;
var isDown = false;
var startX = 0;
var scrollLeft = 0;
el.addEventListener('mousedown', function(e){
if (e.button !== 0) return;
if (e.target && e.target.closest('a,button,input,select,textarea,label,.dropdown-menu')) return;
isDown = true;
el.classList.add('clients-grabbing');
startX = e.pageX;
scrollLeft = el.scrollLeft;
});
document.addEventListener('mouseup', function(){
if (!isDown) return;
isDown = false;
el.classList.remove('clients-grabbing');
});
document.addEventListener('mousemove', function(e){
if (!isDown) return;
var walk = (e.pageX - startX);
el.scrollLeft = scrollLeft - walk;
});
})();
</script>
<!-- Quick Allocate Modal -->
<div class="modal fade" id="quickAllocateModal" tabindex="-1" aria-labelledby="quickAllocateLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="quickAllocateLabel"><i class="fa-solid fa-map-pin me-2"></i>Allocate Plot</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" action="allocation-add.php">
<div class="modal-body">
<input type="hidden" name="client_id" id="alloc_client_id" value="">
<div class="mb-3">
<label class="form-label fw-bold">Client</label>
<input type="text" id="alloc_client_name" class="form-control" readonly>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Property</label>
<select class="form-select" name="property_id" required>
<option value="">Select property...</option>
<?php foreach ($propertiesAvail as $pp): ?>
<option value="<?= (int)$pp['id'] ?>"><?= htmlspecialchars($pp['title']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Plot Number</label>
<input type="text" class="form-control" name="plot_number" placeholder="e.g. Plot 12A">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Plot Size</label>
<input type="text" class="form-control" name="plot_size" placeholder="e.g. 500SQM">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Allocation Date</label>
<input type="date" class="form-control" name="allocation_date" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Notes</label>
<textarea class="form-control" name="notes" rows="2" placeholder="Optional notes..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Allocation</button>
</div>
</form>
</div>
</div>
<script>
function setAllocateClient(id, name) {
document.getElementById('alloc_client_id').value = id;
document.getElementById('alloc_client_name').value = name;
}
</script>
</div>
<div class="modal fade" id="addClientModal" tabindex="-1" aria-labelledby="addClientLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addClientLabel"><i class="fa-solid fa-user-plus me-2"></i>Add Client</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="add_client_from_list" value="1">
<div class="mb-3">
<label class="form-label fw-bold">Full Name</label>
<input type="text" name="new_name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Email</label>
<input type="email" name="new_email" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Phone Number</label>
<input type="text" name="new_phone" class="form-control">
</div>
<div class="mb-3">
<label class="form-label fw-bold">Location</label>
<input type="text" name="new_location" class="form-control">
</div>
<div class="mb-3">
<label class="form-label fw-bold">Estate / Property Interest</label>
<input type="text" name="new_interest" class="form-control" placeholder="Estate or property name">
</div>
<div class="mb-3">
<label class="form-label fw-bold">Plot Size (sqm)</label>
<input type="number" name="new_plot_sqm" class="form-control" min="0" step="1">
</div>
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label fw-bold">Birthday</label>
<input type="date" name="new_dob" class="form-control" required>
</div>
<div class="col-12 col-md-6">
<label class="form-label fw-bold">Anniversary (optional)</label>
<input type="date" name="new_anniversary" class="form-control">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Referral Source</label>
<select name="new_referral" class="form-select">
<option value="">Select source</option>
<option>Marketer</option>
<option>Contact Centre</option>
<option>Referral</option>
<option>Direct Client</option>
<option>Event</option>
<option>Social Media</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Notes (optional)</label>
<textarea name="new_notes" class="form-control" rows="3"></textarea>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="go_onboarding_clients" name="go_onboarding" value="1" checked>
<label class="form-check-label" for="go_onboarding_clients">Complete onboarding now after creating this client</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</div>
</div>
</div>
<!-- SIDE DRAWER -->
<div class="side-drawer-backdrop" id="drawerBackdrop" onclick="closeClientDetails()"></div>
<div class="side-drawer" id="sideDrawer">
<!-- Header -->
<div class="p-4 border-bottom d-flex justify-content-between align-items-center bg-white sticky-top">
<h5 class="mb-0 fw-bold text-navy">Client Profile</h5>
<button type="button" class="btn-close" onclick="closeClientDetails()"></button>
</div>
<!-- Content -->
<div class="p-4 flex-grow-1" id="drawerContent">
<div class="text-center py-5"><div class="spinner-border text-navy" role="status"></div></div>
</div>
</div>
<div class="modal fade" id="createDealModal" tabindex="-1" aria-labelledby="createDealLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createDealLabel"><i class="fa-solid fa-file-invoice-dollar me-2"></i>Create Deal</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="post" enctype="multipart/form-data">
<div class="modal-body">
<input type="hidden" name="create_deal_from_client" value="1">
<input type="hidden" name="deal_client_id" id="deal_client_id">
<div class="mb-2">
<label class="form-label">Client Name</label>
<input type="text" name="deal_client_name" id="deal_client_name" class="form-control" readonly>
</div>
<div class="mb-2">
<label class="form-label">Property / Estate</label>
<input type="text" name="property_estate" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">Project / Property Description</label>
<textarea name="project_desc" class="form-control" rows="2"></textarea>
</div>
<div class="row g-2">
<div class="col-md-4">
<label class="form-label">Amount Offered</label>
<input type="number" step="0.01" min="0" name="amount_offered" id="amount_offered" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Amount Paid Now</label>
<input type="number" step="0.01" min="0" name="amount_paid_now" id="amount_paid_now" class="form-control" required>
</div>
<div class="col-md-4">
<label class="form-label">Discount Amount</label>
<input type="number" step="0.01" min="0" name="discount_amount" id="discount_amount" class="form-control">
</div>
</div>
<div class="mb-2">
<label class="form-label">Upload Payment Receipt</label>
<input type="file" name="receipt_file" class="form-control" accept=".pdf,image/*">
</div>
<div class="border rounded p-2 mt-2">
<div class="d-flex justify-content-between align-items-center">
<div class="fw-bold small">Transaction History</div>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addTxn()">Add</button>
</div>
<div id="txnList" class="mt-2"></div>
</div>
<div class="row g-2 mt-2">
<div class="col-md-3">
<label class="form-label">Commission %</label>
<input type="number" step="0.01" min="0" name="commission_pct" id="commission_pct" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Marketer Commission</label>
<input type="number" step="0.01" min="0" name="marketer_comm" id="marketer_comm" class="form-control" readonly>
</div>
<div class="col-md-4">
<label class="form-label">Agent Commission</label>
<input type="number" step="0.01" min="0" name="agent_comm" id="agent_comm" class="form-control" readonly>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary" <?= in_array($role, ['marketer','contact_rep','internal_staff','operations','admin','super_admin']) ? '' : 'disabled' ?>>Submit</button>
</div>
</form>
</div>
</div>
</div>
<script>
function openClientDetails(id) {
window.location.href = 'client-profile.php?client_id=' + encodeURIComponent(id);
}
function closeClientDetails() {
document.getElementById('drawerBackdrop').classList.remove('show');
document.getElementById('sideDrawer').classList.remove('open');
document.body.style.overflow = '';
setTimeout(() => {
document.getElementById('drawerContent').innerHTML = '<div class="text-center py-5"><div class="spinner-border text-navy" role="status"></div></div>';
}, 300);
}
function setDealClient(id, name) {
document.getElementById('deal_client_id').value = id;
document.getElementById('deal_client_name').value = name;
}
function sendPaymentReminder(clientId) {
if (!confirm('Send payment reminder notification to this client?')) return;
fetch('ajax_send_reminder.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'client_id=' + clientId + '&type=payment_reminder'
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Reminder sent successfully!');
} else {
alert('Failed to send reminder: ' + (data.message || 'Unknown error'));
}
})
.catch(err => {
console.error(err);
alert('An error occurred while sending the reminder.');
});
}
function renderClientDetails(data) {
const client = data.client;
const stats = data.stats;
const allocs = data.allocations;
const docs = data.documents;
const form = data.form || {};
let html = `
<div class="text-center mb-4">
${client.passport_url ? `<img src="${client.passport_url}" alt="Passport" class="rounded-circle mb-3" style="width:96px;height:96px;object-fit:cover;">` : `<div class="avatar-ring avatar-xl mx-auto mb-3">${client.name.charAt(0).toUpperCase()}</div>`}
<h4 class="fw-bold mb-1">${client.name}</h4>
<div class="text-muted small">${client.email}</div>
<div class="chip chip-muted mt-2"><i class="fa-regular fa-calendar me-1"></i>Joined: ${new Date(client.created_at).toLocaleDateString()}</div>
<div class="chip chip-muted mt-2"><i class="fa-solid fa-phone me-1"></i>${client.phone || '-'}</div>
<div class="chip chip-muted mt-2"><i class="fa-solid fa-location-dot me-1"></i>${client.address || '-'}</div>
</div>
<!-- Financial Stats -->
<div class="row g-2 mb-4 text-center">
<div class="col-6">
<div class="p-3 border rounded bg-light">
<div class="text-muted small">Total Properties</div>
<div class="fw-bold fs-5">${stats.allocations_count}</div>
</div>
</div>
<div class="col-6">
<div class="p-3 border rounded bg-light">
<div class="text-muted small">Total Paid</div>
<div class="fw-bold fs-5 text-navy">₦${(stats.total_paid/1000000).toFixed(2)}M</div>
</div>
</div>
</div>
<!-- Allocations List -->
<h6 class="text-uppercase text-muted small fw-bold mb-3 border-bottom pb-2">Allocations</h6>
<div class="mb-4">
${allocs.length > 0 ? allocs.map(a => `
<div class="card mb-2 border-light shadow-sm">
<div class="card-body p-3">
<div class="d-flex justify-content-between mb-1">
<span class="fw-bold">${a.property_title}</span>
${getStatusBadgeHTML(a.status)}
</div>
<div class="d-flex justify-content-between text-muted small">
<span>₦${Number(a.property_price).toLocaleString()}</span>
<span>${new Date(a.created_at).toLocaleDateString()}</span>
</div>
</div>
</div>
`).join('') : '<div class="text-muted fst-italic text-center small">No active allocations</div>'}
</div>
<!-- Registration -->
<h6 class="text-uppercase text-muted small fw-bold mb-3 border-bottom pb-2">Registration</h6>
<div class="mb-4">
<div class="row g-2">
<div class="col-6"><span class="text-muted small">Applicant Status</span><div>${form.applicant_status || '-'}</div></div>
<div class="col-6"><span class="text-muted small">Purpose</span><div>${form.purpose || '-'}</div></div>
<div class="col-6"><span class="text-muted small">DOB</span><div>${form.dob || '-'}</div></div>
<div class="col-6"><span class="text-muted small">Nationality</span><div>${form.nationality || '-'}</div></div>
<div class="col-6"><span class="text-muted small">Employer</span><div>${form.employer || '-'}</div></div>
<div class="col-6"><span class="text-muted small">Occupation</span><div>${form.occupation || '-'}</div></div>
<div class="col-6"><span class="text-muted small">WhatsApp Number</span><div>${form.whatsapp_phone || '-'}</div></div>
<div class="col-6"><span class="text-muted small">Office Number</span><div>${form.office_phone || '-'}</div></div>
<div class="col-6"><span class="text-muted small">Residential Address</span><div>${form.residential_address || form.address || '-'}</div></div>
<div class="col-6"><span class="text-muted small">Office Address</span><div>${form.office_address || '-'}</div></div>
</div>
<div class="mt-2 d-flex gap-2">
${data.receipt_path ? `<a target="_blank" href="${data.receipt_path}" class="btn btn-sm btn-light"><i class="fa-solid fa-receipt me-1"></i>Receipt</a>` : ``}
${client.passport_url ? `<a target="_blank" href="${client.passport_url}" class="btn btn-sm btn-light"><i class="fa-regular fa-id-badge me-1"></i>Passport</a>` : ``}
${client.id_document_path ? `<a target="_blank" href="${client.id_document_path}" class="btn btn-sm btn-light"><i class="fa-solid fa-id-card me-1"></i>ID Document</a>` : ``}
</div>
<div class="mt-3">
<?php if (in_array($role, ['contact_rep','customer_rep','marketing','sales','admin','super_admin','estate_manager'])): ?>
<a href="documents.php?view=list&open_upload=1&client_id=${client.id}&prefill_title=Offer%20Letter" class="btn btn-primary btn-sm">
<i class="fa-solid fa-cloud-arrow-up me-1"></i>Upload Offer Letter
</a>
<?php endif; ?>
</div>
</div>
<!-- Documents -->
<h6 class="text-uppercase text-muted small fw-bold mb-3 border-bottom pb-2">Documents</h6>
<div class="mb-4">
<ul class="list-group list-group-flush">
${docs.length > 0 ? docs.map(d => `
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<div>
<i class="fa-regular fa-file-pdf me-2 text-danger"></i>
<span class="small">${d.title}</span>
</div>
<a href="${d.file_path}" target="_blank" class="btn btn-sm btn-light"><i class="fa-solid fa-download"></i></a>
</li>
`).join('') : '<li class="list-group-item px-0 text-muted fst-italic small text-center">No documents found</li>'}
</ul>
</div>
`;
document.getElementById('drawerContent').innerHTML = html;
}
function getStatusBadgeHTML(status) {
const map = {
'approved': { icon: 'fa-check-circle', class: 'status-badge-approved' },
'completed': { icon: 'fa-check-circle', class: 'status-badge-completed' },
'paid': { icon: 'fa-check-double', class: 'status-badge-paid' },
'part_paid': { icon: 'fa-chart-pie', class: 'status-badge-part-paid' },
'pending': { icon: 'fa-clock', class: 'status-badge-pending' },
'rejected': { icon: 'fa-circle-xmark', class: 'status-badge-rejected' },
'cancelled': { icon: 'fa-ban', class: 'status-badge-cancelled' },
'reserved': { icon: 'fa-bookmark', class: 'status-badge-reserved' }
};
const key = status.toLowerCase();
const config = map[key] || { icon: 'fa-circle', class: 'badge bg-secondary text-white' };
if (!map[key]) return `<span class="badge bg-secondary text-white">${status}</span>`;
// Capitalize first letter
const label = status.charAt(0).toUpperCase() + status.slice(1).replace('_', ' ');
return `<span class="status-badge ${config.class}"><i class="fa-solid ${config.icon}"></i> ${label}</span>`;
}
function addTxn(){
var c = document.getElementById('txnList');
var row = document.createElement('div');
row.className = 'row g-2 align-items-end mb-2';
row.innerHTML = '<div class="col-md-4"><input type="date" name="txn_date[]" class="form-control"></div>'
+ '<div class="col-md-4"><input type="number" step="0.01" min="0" name="txn_amount[]" class="form-control" placeholder="Amount"></div>'
+ '<div class="col-md-4"><button type="button" class="btn btn-outline-danger btn-sm" onclick="this.closest(\'div.row\').remove()">Remove</button></div>';
c.appendChild(row);
}
(function(){
function recalc(){
var offered = parseFloat(document.getElementById('amount_offered').value || '0');
var discount = parseFloat(document.getElementById('discount_amount').value || '0');
var base = Math.max(0, offered - discount);
var pct = parseFloat(document.getElementById('commission_pct').value || '0');
var totalComm = base * (pct/100);
document.getElementById('marketer_comm').value = (totalComm * 0.5).toFixed(2);
document.getElementById('agent_comm').value = (totalComm * 0.5).toFixed(2);
}
['amount_offered','discount_amount','commission_pct'].forEach(function(n){
var el = document.getElementById(n);
if (el) el.addEventListener('input', recalc);
});
})();
(function(){
var params = new URLSearchParams(window.location.search);
var openId = params.get('open_deal_for');
if (openId) {
var nameCell = document.querySelector('tbody tr .fw-bold.text-dark span');
var nameText = nameCell ? nameCell.textContent : '';
setDealClient(openId, nameText);
var m = new bootstrap.Modal(document.getElementById('createDealModal'));
m.show();
}
})();
(function(){
var params = new URLSearchParams(window.location.search);
var cid = params.get('open_client_id');
if (cid && /^\d+$/.test(cid)) {
window.location.href = 'client-profile.php?client_id=' + encodeURIComponent(cid);
}
})();
</script>
<?php include 'includes/footer.php'; ?>