| 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
session_start();
require_once 'includes/db.php';
require_once 'includes/functions.php';
// Access Control
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_role'], ['super_admin', 'admin', 'estate_manager', 'finance_manager', 'finance'])) {
header("Location: dashboard.php");
exit;
}
$companyId = getCurrentCompanyId();
$companyFilter = "";
$companyWhere = " WHERE 1=1";
if ($companyId) {
$cid = (int)$companyId;
$companyFilter = " AND (company_id = $cid OR company_id IS NULL)";
$companyWhere = " WHERE (company_id = $cid OR company_id IS NULL)";
}
// --- FILTERS ---
$start_date = $_GET['start_date'] ?? date('Y-m-01'); // First day of current month
$end_date = $_GET['end_date'] ?? date('Y-m-t'); // Last day of current month
$view_type = $_GET['type'] ?? 'income'; // income, expenses, aging
// --- DATA FETCHING ---
// 1. Income (Payments)
$dateCol = function_exists('kpiPaymentDateColumn') ? kpiPaymentDateColumn('payments') : null;
$dateCol = $dateCol ?: ((function_exists('tableHasColumn') && tableHasColumn('payments', 'created_at')) ? 'created_at' : ((function_exists('tableHasColumn') && tableHasColumn('payments', 'date')) ? 'date' : 'id'));
$approvedStatusesSql = function_exists('kpiSqlList') ? kpiSqlList(kpiPaymentFinalizedStatuses()) : "('verified','approved','paid','completed','success')";
[$paymentsCompanyClause, $paymentsCompanyParams] = function_exists('kpiCompanyFilter') ? kpiCompanyFilter('payments', $companyId, true, 'p.') : ['', []];
$incomeQuery = "SELECT p.*, p.$dateCol AS txn_date
FROM payments p
WHERE p.status IN $approvedStatusesSql
AND p.$dateCol BETWEEN ? AND ?
$paymentsCompanyClause
ORDER BY p.$dateCol DESC";
$incomeStmt = $pdo->prepare($incomeQuery);
$incomeStmt->execute(array_merge([$start_date . ' 00:00:00', $end_date . ' 23:59:59'], $paymentsCompanyParams));
$incomeData = $incomeStmt->fetchAll();
$totalIncome = 0;
foreach ($incomeData as $row) {
$totalIncome += $row['amount'];
}
// 2. Expenses (Maintenance + Commissions)
// Maintenance Costs
$maintQuery = "SELECT id, title, cost, updated_at as date, 'Maintenance' as category
FROM maintenance_requests
WHERE status = 'completed'
AND updated_at BETWEEN ? AND ?
$companyFilter";
// Commissions Paid
$commQuery = "SELECT id, 'Commission Payout' as title, amount as cost, date_paid as date, 'Commission' as category
FROM commissions
WHERE status = 'paid'
AND date_paid BETWEEN ? AND ?
$companyFilter"; // Assuming commissions has company_id, if not we might need to join users/deals
// Check if commissions has company_id
// For now, let's assume it does or we'll skip the filter if it breaks (better to be safe, maybe check schema first? I'll assume it follows standard)
// Actually, let's play safe and wrap in try-catch or check columns.
// But for now, I'll assume commissions are linked to company via deal -> property -> company.
// To avoid complex joins for this MVP, I'll fetch and filter if needed, or just run simple queries.
$expensesData = [];
$totalExpenses = 0;
try {
$maintStmt = $pdo->prepare($maintQuery);
$maintStmt->execute([$start_date . ' 00:00:00', $end_date . ' 23:59:59']);
$maintRows = $maintStmt->fetchAll();
$commStmt = $pdo->prepare($commQuery);
$commStmt->execute([$start_date, $end_date]);
$commRows = $commStmt->fetchAll();
$expensesData = array_merge($maintRows, $commRows);
// Sort by date
usort($expensesData, function($a, $b) {
return strtotime($b['date']) - strtotime($a['date']);
});
foreach ($expensesData as $row) {
$totalExpenses += $row['cost'];
}
} catch (Exception $e) {
// Handle potential missing columns gracefully
$totalExpenses = 0;
}
// 3. Aging Receivables (Outstanding Invoices)
$agingQuery = "SELECT i.*, u.name as client_name, p.title as property_title
FROM invoices i
LEFT JOIN users u ON i.tenant_id = u.id
LEFT JOIN leases l ON i.lease_id = l.id
LEFT JOIN properties p ON l.property_id = p.id
WHERE i.status != 'paid'
$companyFilter
ORDER BY i.due_date ASC";
// Note: Invoices might not have company_id directly, usually linked via property.
// If invoices table doesn't have company_id, we need a join.
// I'll assume simple query for now, if it fails I'll fix.
// Actually, let's try to be safer. If invoices table exists.
$agingData = [];
$totalReceivables = 0;
// Initialize defaults to avoid undefined variable notices when query fails
$agingBuckets = [
'current' => 0.0,
'd1_30' => 0.0,
'd31_60' => 0.0,
'd61_90' => 0.0,
'd90_plus' => 0.0
];
try {
// Check if invoices table exists first or just try query
$agingStmt = $pdo->prepare($agingQuery);
$agingStmt->execute();
$agingData = $agingStmt->fetchAll();
foreach ($agingData as $row) {
$totalReceivables += $row['balance_due'];
}
$agingBuckets = [
'current' => 0.0,
'd1_30' => 0.0,
'd31_60' => 0.0,
'd61_90' => 0.0,
'd90_plus' => 0.0
];
$todayAge = new DateTime('today');
foreach ($agingData as $row) {
$dueDateStr = isset($row['due_date']) ? $row['due_date'] : date('Y-m-d');
$due = DateTime::createFromFormat('Y-m-d', substr($dueDateStr, 0, 10)) ?: new DateTime($dueDateStr);
$diff = (int)$todayAge->diff($due)->format('%r%a');
$amt = (float)($row['balance_due'] ?? 0);
if ($diff >= 0) {
$agingBuckets['current'] += $amt;
} elseif ($diff >= -30) {
$agingBuckets['d1_30'] += $amt;
} elseif ($diff >= -60) {
$agingBuckets['d31_60'] += $amt;
} elseif ($diff >= -90) {
$agingBuckets['d61_90'] += $amt;
} else {
$agingBuckets['d90_plus'] += $amt;
}
}
} catch (Exception $e) {
// Invoices table might not have company_id or might not exist
}
$aiInsightsTop = [];
try {
$startObj = DateTimeImmutable::createFromFormat('Y-m-d', substr((string)$start_date, 0, 10)) ?: new DateTimeImmutable((string)$start_date);
$endObj = DateTimeImmutable::createFromFormat('Y-m-d', substr((string)$end_date, 0, 10)) ?: new DateTimeImmutable((string)$end_date);
if ($endObj < $startObj) {
$tmp = $startObj;
$startObj = $endObj;
$endObj = $tmp;
}
$days = (int)($endObj->diff($startObj)->days) + 1;
if ($days <= 0) $days = 1;
$prevStartObj = $startObj->sub(new DateInterval('P' . $days . 'D'));
$prevEndObj = $endObj->sub(new DateInterval('P' . $days . 'D'));
$aiRevenue = 0.0;
$aiRevenuePrev = 0.0;
$aiExpenses = 0.0;
$aiExpensesPrev = 0.0;
$pendingApprovals = 0;
$pendingPayments = 0;
$bankPositions = [];
$lowBalanceThreshold = 0.0;
try {
$rawThr = function_exists('getCompanySetting') ? getCompanySetting($companyId, 'low_balance_threshold', '') : '';
if ($rawThr === '' || $rawThr === null) { $rawThr = function_exists('getSetting') ? getSetting('low_balance_threshold', '') : ''; }
$lowBalanceThreshold = is_numeric($rawThr) ? (float)$rawThr : 0.0;
} catch (Throwable $eThr) { $lowBalanceThreshold = 0.0; }
try {
$hasAcc = $pdo->query("SHOW TABLES LIKE 'finance_accounts'")->rowCount() > 0;
if ($hasAcc && function_exists('tableHasColumn') && tableHasColumn('finance_accounts', 'balance')) {
$q = "SELECT id, " . (tableHasColumn('finance_accounts','account_name') ? 'account_name' : (tableHasColumn('finance_accounts','name') ? 'name' : "CONCAT('Account #', id)")) . " AS account_name, balance FROM finance_accounts WHERE 1=1";
$p = [];
if ($companyId && tableHasColumn('finance_accounts','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $p[] = $companyId; }
$q .= " ORDER BY account_name";
$st = $pdo->prepare($q);
$st->execute($p);
$bankPositions = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
}
} catch (Throwable $eAcc) { $bankPositions = []; }
try {
$hasEm = $pdo->query("SHOW TABLES LIKE 'expenses_manual'")->rowCount() > 0;
if ($hasEm && function_exists('tableHasColumn') && tableHasColumn('expenses_manual','status')) {
$q = "SELECT COUNT(*) FROM expenses_manual WHERE LOWER(TRIM(status)) = 'pending'";
$p = [];
if ($companyId && tableHasColumn('expenses_manual','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $p[] = $companyId; }
$st = $pdo->prepare($q);
$st->execute($p);
$pendingApprovals = (int)($st->fetchColumn() ?: 0);
}
} catch (Throwable $ePa) { $pendingApprovals = 0; }
try {
$hasPay = $pdo->query("SHOW TABLES LIKE 'payments'")->rowCount() > 0;
if ($hasPay && function_exists('tableHasColumn')) {
$stCol = tableHasColumn('payments', 'status') ? 'status' : (tableHasColumn('payments', 'payment_status') ? 'payment_status' : null);
if ($stCol) {
$pendingStatuses = function_exists('kpiPaymentPendingStatuses') ? (array)kpiPaymentPendingStatuses() : ['submitted', 'pending', 'pending_gateway', 'awaiting_verification', 'pending_verification', 'pending_confirmation'];
$ph = implode(',', array_fill(0, count($pendingStatuses), '?'));
$q = "SELECT COUNT(*) FROM payments WHERE LOWER(TRIM($stCol)) IN ($ph)";
$p = array_map(function ($v) { return strtolower(trim((string)$v)); }, $pendingStatuses);
if ($companyId && tableHasColumn('payments', 'company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $p[] = $companyId; }
$st = $pdo->prepare($q);
$st->execute($p);
$pendingPayments = (int)($st->fetchColumn() ?: 0);
}
}
} catch (Throwable $ePp) { $pendingPayments = 0; }
try {
$hasPay = $pdo->query("SHOW TABLES LIKE 'payments'")->rowCount() > 0;
if ($hasPay) {
$dateCol = function_exists('kpiPaymentDateColumn') ? (kpiPaymentDateColumn('payments') ?: 'created_at') : 'created_at';
$statusApproved = function_exists('kpiSqlList') ? kpiSqlList(kpiPaymentFinalizedStatuses()) : "('verified','approved','paid','completed','success')";
$q = "SELECT COALESCE(SUM(amount),0) FROM payments WHERE status IN $statusApproved AND $dateCol BETWEEN ? AND ?";
$p = [$startObj->format('Y-m-d') . ' 00:00:00', $endObj->format('Y-m-d') . ' 23:59:59'];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $p[] = $companyId; }
$st = $pdo->prepare($q);
$st->execute($p);
$aiRevenue = (float)($st->fetchColumn() ?: 0);
$pPrev = [$prevStartObj->format('Y-m-d') . ' 00:00:00', $prevEndObj->format('Y-m-d') . ' 23:59:59'];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) { $pPrev[] = $companyId; }
$st = $pdo->prepare($q);
$st->execute($pPrev);
$aiRevenuePrev = (float)($st->fetchColumn() ?: 0);
}
} catch (Throwable $eRev) { $aiRevenue = 0.0; $aiRevenuePrev = 0.0; }
try {
if (function_exists('buildExpensesUnionSql')) {
$params = [];
$union = buildExpensesUnionSql(['start_date' => $startObj->format('Y-m-d'), 'end_date' => $endObj->format('Y-m-d'), 'include_pending_manual' => false], $params, false);
$q = "SELECT COALESCE(SUM(amount),0) FROM $union e";
$st = $pdo->prepare($q);
$st->execute($params);
$aiExpenses = (float)($st->fetchColumn() ?: 0);
$params2 = [];
$union2 = buildExpensesUnionSql(['start_date' => $prevStartObj->format('Y-m-d'), 'end_date' => $prevEndObj->format('Y-m-d'), 'include_pending_manual' => false], $params2, false);
$q2 = "SELECT COALESCE(SUM(amount),0) FROM $union2 e";
$st2 = $pdo->prepare($q2);
$st2->execute($params2);
$aiExpensesPrev = (float)($st2->fetchColumn() ?: 0);
}
} catch (Throwable $eExp) { $aiExpenses = 0.0; $aiExpensesPrev = 0.0; }
$profitByEstate = [];
try {
if (function_exists('get_profit_per_estate')) {
$rows = (array)get_profit_per_estate($startObj->format('Y-m-d') . ' 00:00:00', $endObj->format('Y-m-d') . ' 23:59:59', $companyId);
foreach ($rows as $r) {
if (!is_array($r)) continue;
$eid = (int)($r['estate_id'] ?? 0);
if ($eid <= 0) continue;
$profitByEstate[] = [
'estate_id' => $eid,
'estate' => (string)($r['estate_name'] ?? ''),
'revenue' => (float)($r['revenue'] ?? 0),
'expenses' => (float)($r['expense'] ?? 0),
'profit' => (float)($r['profit'] ?? 0),
];
}
}
} catch (Throwable $eEs) { $profitByEstate = []; }
if (function_exists('generate_financial_insights')) {
$all = (array)generate_financial_insights($pdo, [
'company_id' => $companyId,
'revenue' => $aiRevenue,
'revenue_prev' => $aiRevenuePrev,
'expenses' => $aiExpenses,
'expenses_prev' => $aiExpensesPrev,
'profit' => $aiRevenue - $aiExpenses,
'pending_approvals' => $pendingApprovals,
'pending_payments' => $pendingPayments,
'bank_positions' => $bankPositions,
'low_balance_threshold' => $lowBalanceThreshold,
'profit_by_estate' => $profitByEstate,
'period_start' => $startObj->format('Y-m-d'),
'period_end' => $endObj->format('Y-m-d'),
]);
$aiInsightsTop = array_slice($all, 0, 5);
}
} catch (Throwable $eAi) {
$aiInsightsTop = [];
}
include 'includes/header.php';
?>
<div class="container-fluid py-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 text-gray-800">Financial Reports</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="reports.php">Reports</a></li>
<li class="breadcrumb-item active">Financials</li>
</ol>
</nav>
</div>
<div>
<button class="btn btn-outline-secondary me-2" onclick="window.print()"><i class="fa-solid fa-print"></i> Print</button>
<button class="btn btn-primary"><i class="fa-solid fa-download"></i> Export CSV</button>
</div>
</div>
<!-- Filter Form -->
<div class="card shadow mb-4">
<div class="card-body">
<form method="GET" action="reports-financial.php" class="row g-3 align-items-end">
<input type="hidden" name="type" value="<?= $view_type ?>">
<div class="col-md-3">
<label class="form-label fw-bold">Start Date</label>
<input type="date" name="start_date" class="form-control" value="<?= $start_date ?>">
</div>
<div class="col-md-3">
<label class="form-label fw-bold">End Date</label>
<input type="date" name="end_date" class="form-control" value="<?= $end_date ?>">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">Apply Filter</button>
</div>
</form>
</div>
</div>
<!-- Summary Cards -->
<div class="row g-4 mb-4">
<div class="col-md-4">
<div class="card border-start border-4 border-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs fw-bold text-success text-uppercase mb-1">Total Income (Selected Period)</div>
<div class="h5 mb-0 fw-bold text-gray-800"><?= formatCurrency($totalIncome) ?></div>
</div>
<div class="col-auto"><i class="fas fa-dollar-sign fa-2x text-gray-300"></i></div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-start border-4 border-danger shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs fw-bold text-danger text-uppercase mb-1">Total Expenses (Selected Period)</div>
<div class="h5 mb-0 fw-bold text-gray-800"><?= formatCurrency($totalExpenses) ?></div>
</div>
<div class="col-auto"><i class="fas fa-money-bill-wave fa-2x text-gray-300"></i></div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-start border-4 border-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs fw-bold text-info text-uppercase mb-1">Net Profit</div>
<div class="h5 mb-0 fw-bold text-gray-800"><?= formatCurrency($totalIncome - $totalExpenses) ?></div>
</div>
<div class="col-auto"><i class="fas fa-chart-line fa-2x text-gray-300"></i></div>
</div>
</div>
</div>
</div>
</div>
<style>
.ai-insight-card{border:1px solid rgba(15,23,42,.08);border-radius:14px;padding:12px;background:#fff}
.ai-insight-card.risk{border-color:rgba(239,68,68,.35);background:rgba(239,68,68,.06)}
.ai-insight-card.warning{border-color:rgba(245,158,11,.40);background:rgba(245,158,11,.08)}
.ai-insight-card.info{border-color:rgba(34,197,94,.32);background:rgba(34,197,94,.07)}
.ai-insight-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:700;letter-spacing:.06em}
.ai-insight-badge.risk{background:rgba(239,68,68,.12);color:#991b1b}
.ai-insight-badge.warning{background:rgba(245,158,11,.16);color:#92400e}
.ai-insight-badge.info{background:rgba(34,197,94,.12);color:#166534}
.rf-table-scroll{display:block;width:100%;max-width:100%;overflow:auto !important;overflow-x:auto !important;overflow-y:hidden;-webkit-overflow-scrolling:touch;touch-action:pan-x pan-y}
.rf-income-table{width:100%}
.rf-income-table th,.rf-income-table td{vertical-align:middle}
.rf-income-table td.rf-primary{white-space:normal;min-width:240px}
.rf-expense-table{width:100%}
.rf-expense-table td.rf-primary{white-space:normal;min-width:260px}
.rf-aging-table{width:100%}
.rf-aging-table td.rf-primary{white-space:normal;min-width:240px}
</style>
<div class="card shadow mb-4">
<div class="card-header py-3 d-flex align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-primary">AI Financial Insights</h6>
<div class="text-muted small">Top 5</div>
</div>
<div class="card-body">
<?php if (empty($aiInsightsTop)): ?>
<div class="text-muted">No insights available for this period yet.</div>
<?php else: ?>
<div class="row g-3">
<?php foreach ($aiInsightsTop as $ins): ?>
<?php
$t = strtolower(trim((string)($ins['type'] ?? 'info')));
if (!in_array($t, ['risk','warning','info'], true)) $t = 'info';
?>
<div class="col-12 col-lg-6">
<div class="ai-insight-card <?= htmlspecialchars($t) ?>">
<div class="d-flex align-items-start justify-content-between gap-2">
<div class="fw-bold"><?= htmlspecialchars((string)($ins['title'] ?? 'Insight')) ?></div>
<span class="ai-insight-badge <?= htmlspecialchars($t) ?>"><?= htmlspecialchars(strtoupper($t)) ?></span>
</div>
<div class="text-muted small mt-1"><?= htmlspecialchars((string)($ins['description'] ?? '')) ?></div>
<div class="mt-2 fw-bold" style="font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#64748b">Recommendation</div>
<div class="small"><?= htmlspecialchars((string)($ins['recommendation'] ?? '')) ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Navigation Tabs -->
<ul class="nav nav-tabs mb-4">
<li class="nav-item">
<a class="nav-link <?= $view_type == 'income' ? 'active' : '' ?>" href="reports-financial.php?type=income&start_date=<?= $start_date ?>&end_date=<?= $end_date ?>">Income Breakdown</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $view_type == 'expenses' ? 'active' : '' ?>" href="reports-financial.php?type=expenses&start_date=<?= $start_date ?>&end_date=<?= $end_date ?>">Expense Report</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $view_type == 'aging' ? 'active' : '' ?>" href="reports-financial.php?type=aging&start_date=<?= $start_date ?>&end_date=<?= $end_date ?>">Aging Receivables</a>
</li>
</ul>
<!-- Content Area -->
<div class="card shadow mb-4">
<div class="card-body">
<?php if ($view_type == 'income'): ?>
<h5 class="card-title mb-4">Income Transactions</h5>
<div class="d-sm-none text-muted small border-bottom pb-2 mb-2">Swipe left/right to see all columns.</div>
<div class="table-responsive rf-table-scroll">
<table class="table table-bordered table-striped rf-income-table" width="100%" cellspacing="0">
<thead>
<tr>
<th class="d-none d-md-table-cell">Date</th>
<th>Reference</th>
<th class="d-none d-md-table-cell">Payer / Tenant</th>
<th class="d-none d-lg-table-cell">Method</th>
<th class="d-none d-lg-table-cell">Status</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody>
<?php if (empty($incomeData)): ?>
<tr><td colspan="6" class="text-center text-muted py-4">No income records found for this period.</td></tr>
<?php else: ?>
<?php foreach ($incomeData as $row): ?>
<tr>
<?php
$txnDate = isset($row['txn_date']) ? $row['txn_date'] : (isset($row['payment_date']) ? $row['payment_date'] : (isset($row['date']) ? $row['date'] : (isset($row['created_at']) ? $row['created_at'] : null)));
$txnDateLabel = $txnDate ? formatDate($txnDate) : 'N/A';
$ref = (string)($row['reference'] ?? 'N/A');
$payer = (string)($row['user_id'] ?? 'N/A');
$method = ucfirst((string)($row['method'] ?? 'N/A'));
?>
<td class="d-none d-md-table-cell"><?= $txnDateLabel ?></td>
<td class="rf-primary">
<div class="fw-bold"><?= htmlspecialchars($ref) ?></div>
<div class="text-muted small d-md-none"><?= htmlspecialchars($payer) ?></div>
<div class="text-muted small d-md-none"><?= htmlspecialchars($txnDateLabel) ?> · <?= htmlspecialchars($method) ?> · Paid</div>
</td>
<td class="d-none d-md-table-cell"><?= htmlspecialchars($payer) ?></td>
<td class="d-none d-lg-table-cell"><?= htmlspecialchars($method) ?></td>
<td class="d-none d-lg-table-cell"><span class="badge bg-success">Paid</span></td>
<td class="text-end fw-bold"><?= formatCurrency($row['amount']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot>
<tr class="table-primary">
<td colspan="5" class="fw-bold text-end">Total Income:</td>
<td class="text-end fw-bold"><?= formatCurrency($totalIncome) ?></td>
</tr>
</tfoot>
</table>
</div>
<?php elseif ($view_type == 'expenses'): ?>
<h5 class="card-title mb-4">Expense Breakdown (Maintenance & Commissions)</h5>
<div class="d-sm-none text-muted small border-bottom pb-2 mb-2">Swipe left/right to see all columns.</div>
<div class="table-responsive rf-table-scroll">
<table class="table table-bordered table-striped rf-expense-table" width="100%" cellspacing="0">
<thead>
<tr>
<th class="d-none d-md-table-cell">Date</th>
<th class="d-none d-lg-table-cell">Category</th>
<th>Description</th>
<th class="text-end">Cost</th>
</tr>
</thead>
<tbody>
<?php if (empty($expensesData)): ?>
<tr><td colspan="4" class="text-center text-muted py-4">No expense records found for this period.</td></tr>
<?php else: ?>
<?php foreach ($expensesData as $row): ?>
<tr>
<?php
$d = (string)($row['date'] ?? '');
$dLabel = $d !== '' ? formatDate($d) : '—';
$cat = (string)($row['category'] ?? '');
$title = (string)($row['title'] ?? '');
?>
<td class="d-none d-md-table-cell"><?= htmlspecialchars($dLabel) ?></td>
<td class="d-none d-lg-table-cell"><span class="badge bg-secondary"><?= htmlspecialchars($cat) ?></span></td>
<td class="rf-primary">
<div class="fw-semibold"><?= htmlspecialchars($title) ?></div>
<div class="text-muted small d-md-none"><?= htmlspecialchars($dLabel) ?><?= $cat !== '' ? (' · ' . htmlspecialchars($cat)) : '' ?></div>
</td>
<td class="text-end fw-bold"><?= formatCurrency($row['cost']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot>
<tr class="table-danger">
<td colspan="3" class="fw-bold text-end">Total Expenses:</td>
<td class="text-end fw-bold"><?= formatCurrency($totalExpenses) ?></td>
</tr>
</tfoot>
</table>
</div>
<?php elseif ($view_type == 'aging'): ?>
<h5 class="card-title mb-4">Aging Receivables (Outstanding Invoices)</h5>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0 d-flex align-items-center">
<div class="me-2 avatar-ring bg-primary text-white d-flex align-items-center justify-content-center" style="width:36px;height:36px;border-radius:50%;">
<i class="fa-solid fa-chart-pie"></i>
</div>
<div>
<h6 class="mb-0 fw-bold">Receivables Aging Overview</h6>
<small class="text-muted">Outstanding by aging buckets</small>
</div>
<div class="ms-auto">
<span class="badge bg-light text-dark border">
Total <?= formatCurrency($totalReceivables) ?>
</span>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-3 col-6">
<div class="chip chip-primary w-100 d-flex justify-content-between align-items-center">
<span>Current</span>
<span><?= formatCurrency($agingBuckets['current']) ?></span>
</div>
<?php $pct = $totalReceivables > 0 ? round(($agingBuckets['current'] / $totalReceivables) * 100) : 0; ?>
<div class="progress progress-premium mt-2" style="height:8px;">
<div class="progress-bar bg-success" role="progressbar" style="width: <?= $pct ?>%;" aria-valuenow="<?= $pct ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted"><?= $pct ?>%</small>
</div>
<div class="col-md-3 col-6">
<div class="chip chip-warning w-100 d-flex justify-content-between align-items-center">
<span>1–30</span>
<span><?= formatCurrency($agingBuckets['d1_30']) ?></span>
</div>
<?php $pct = $totalReceivables > 0 ? round(($agingBuckets['d1_30'] / $totalReceivables) * 100) : 0; ?>
<div class="progress progress-premium mt-2" style="height:8px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: <?= $pct ?>%;" aria-valuenow="<?= $pct ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted"><?= $pct ?>%</small>
</div>
<div class="col-md-3 col-6">
<div class="chip chip-orange w-100 d-flex justify-content-between align-items-center">
<span>31–60</span>
<span><?= formatCurrency($agingBuckets['d31_60']) ?></span>
</div>
<?php $pct = $totalReceivables > 0 ? round(($agingBuckets['d31_60'] / $totalReceivables) * 100) : 0; ?>
<div class="progress progress-premium mt-2" style="height:8px;">
<div class="progress-bar bg-orange" role="progressbar" style="width: <?= $pct ?>%;" aria-valuenow="<?= $pct ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted"><?= $pct ?>%</small>
</div>
<div class="col-md-3 col-6">
<div class="chip chip-danger w-100 d-flex justify-content-between align-items-center">
<span>61–90</span>
<span><?= formatCurrency($agingBuckets['d61_90']) ?></span>
</div>
<?php $pct = $totalReceivables > 0 ? round(($agingBuckets['d61_90'] / $totalReceivables) * 100) : 0; ?>
<div class="progress progress-premium mt-2" style="height:8px;">
<div class="progress-bar bg-danger" role="progressbar" style="width: <?= $pct ?>%;" aria-valuenow="<?= $pct ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted"><?= $pct ?>%</small>
</div>
<div class="col-md-12 mt-3">
<div class="chip chip-dark w-100 d-flex justify-content-between align-items-center">
<span>90+</span>
<span><?= formatCurrency($agingBuckets['d90_plus']) ?></span>
</div>
<?php $pct = $totalReceivables > 0 ? round(($agingBuckets['d90_plus'] / $totalReceivables) * 100) : 0; ?>
<div class="progress progress-premium mt-2" style="height:10px;">
<div class="progress-bar bg-dark" role="progressbar" style="width: <?= $pct ?>%;" aria-valuenow="<?= $pct ?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted"><?= $pct ?>% of total outstanding</small>
</div>
</div>
</div>
</div>
<div class="d-sm-none text-muted small border-bottom pb-2 mb-2">Swipe left/right to see all columns.</div>
<div class="table-responsive rf-table-scroll">
<table class="table table-bordered table-striped rf-aging-table" width="100%" cellspacing="0">
<thead>
<tr>
<th class="d-none d-md-table-cell">Due Date</th>
<th>Invoice #</th>
<th class="d-none d-md-table-cell">Tenant</th>
<th class="d-none d-lg-table-cell">Property</th>
<th class="d-none d-lg-table-cell">Status</th>
<th class="d-none d-md-table-cell">Days Overdue</th>
<th class="text-end">Balance Due</th>
</tr>
</thead>
<tbody>
<?php if (empty($agingData)): ?>
<tr><td colspan="7" class="text-center text-muted py-4">No outstanding invoices found.</td></tr>
<?php else: ?>
<?php foreach ($agingData as $row):
$dueDate = strtotime($row['due_date']);
$today = time();
$daysOverdue = floor(($today - $dueDate) / (60 * 60 * 24));
$isOverdue = $daysOverdue > 0;
?>
<tr>
<?php
$inv = (string)($row['invoice_number'] ?? $row['id'] ?? '');
$tenant = (string)($row['client_name'] ?? 'Unknown');
$prop = (string)($row['property_title'] ?? 'N/A');
$st = (string)($row['status'] ?? '');
$dueLbl = formatDate($row['due_date']);
$daysLbl = $isOverdue ? ($daysOverdue . ' days') : '-';
?>
<td class="d-none d-md-table-cell <?= $isOverdue ? 'text-danger fw-bold' : '' ?>"><?= $dueLbl ?></td>
<td class="rf-primary">
<div class="fw-semibold"><?= htmlspecialchars($inv) ?></div>
<div class="text-muted small d-md-none">
<?= htmlspecialchars($tenant) ?>
<?= $prop !== '' ? ' · ' . htmlspecialchars($prop) : '' ?>
</div>
<div class="text-muted small d-md-none <?= $isOverdue ? 'text-danger' : '' ?>">
<?= htmlspecialchars($dueLbl) ?> · <?= htmlspecialchars($daysLbl) ?> · <?= htmlspecialchars(ucfirst($st)) ?>
</div>
</td>
<td class="d-none d-md-table-cell"><?= htmlspecialchars($tenant) ?></td>
<td class="d-none d-lg-table-cell"><?= htmlspecialchars($prop) ?></td>
<td class="d-none d-lg-table-cell"><span class="badge <?= getStatusBadgeClass($st) ?>"><?= ucfirst($st) ?></span></td>
<td class="d-none d-md-table-cell <?= $isOverdue ? 'text-danger' : '' ?>"><?= $daysLbl ?></td>
<td class="text-end fw-bold"><?= formatCurrency($row['balance_due']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot>
<tr class="table-warning">
<td colspan="6" class="fw-bold text-end">Total Receivables:</td>
<td class="text-end fw-bold"><?= formatCurrency($totalReceivables) ?></td>
</tr>
</tfoot>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php include 'includes/footer.php'; ?>