| Server IP : 72.60.21.38 / Your IP : 216.73.216.164 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
require 'includes/header.php';
require_once 'includes/db.php';
require_once 'includes/functions.php';
$__plots_fallback_defined = function_exists('ensurePlotsTable');
if (!$__plots_fallback_defined) {
function ensurePlotsTable() {
global $pdo;
try {
$pdo->exec("CREATE TABLE IF NOT EXISTS plots (
id INT AUTO_INCREMENT PRIMARY KEY,
estate_id INT NOT NULL,
block VARCHAR(50) NOT NULL,
plot_number VARCHAR(50) NOT NULL,
size_sqm DECIMAL(12,2) NOT NULL,
status ENUM('available','reserved','allocated','locked','revoked') DEFAULT 'available',
assigned_allocation_id INT NULL,
company_id INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_plot (estate_id, block, plot_number)
)");
} catch (Exception $e) {}
}
}
$isDemo = (getSetting('demo_mode', 'off') === 'on') || (isset($_GET['demo']) && $_GET['demo'] === '1');
$id = $_GET['id'] ?? null;
if (!$id && !$isDemo) {
header('Location: estates.php');
exit;
}
$companyId = getCurrentCompanyId();
$companyFilter = $companyId ? "AND company_id = $companyId" : "";
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && !$isDemo) {
$estateId = (int)($id ?? 0);
$role = strtolower((string)($_SESSION['user_role'] ?? 'guest'));
if (!in_array($role, ['admin','super_admin','estate_manager'])) {
header("Location: estate-details.php?id=" . $estateId . "&msg=forbidden");
exit;
}
$action = $_POST['action'];
if ($action === 'delete_estate') {
try {
deleteEstateSafely($estateId);
header("Location: properties.php?notice=" . urlencode('Estate deleted successfully.') . "&type=success");
exit;
} catch (Throwable $e) {
header("Location: estate-details.php?id=" . $estateId . "&msg=" . urlencode($e->getMessage()));
exit;
}
} elseif ($action === 'plots_generate') {
$block = trim($_POST['block'] ?? '');
$start = (int)($_POST['start_no'] ?? 0);
$end = (int)($_POST['end_no'] ?? 0);
$size = (float)($_POST['size_sqm'] ?? 0);
if ($block !== '' && $start > 0 && $end > 0 && $size > 0) {
try { bulkGeneratePlots($estateId, $block, $start, $end, $size); } catch (Exception $e) {}
header("Location: estate-details.php?id=" . $estateId . "&msg=plots_generated");
exit;
} else {
header("Location: estate-details.php?id=" . $estateId . "&msg=invalid_input");
exit;
}
} elseif ($action === 'plots_add_single') {
$block = trim($_POST['block'] ?? '');
$plotNo = trim($_POST['plot_no'] ?? '');
$size = (float)($_POST['size_sqm'] ?? 0);
if ($block !== '' && $plotNo !== '' && $size > 0) {
try {
ensurePlotsTable();
$cid = getCurrentCompanyId();
$st = $pdo->prepare("INSERT IGNORE INTO plots (estate_id, block, plot_number, size_sqm, status, company_id) VALUES (?, ?, ?, ?, 'available', ?)");
$st->execute([$estateId, $block, $plotNo, $size, $cid]);
} catch (Exception $e) {}
header("Location: estate-details.php?id=" . $estateId . "&msg=plot_added");
exit;
} else {
header("Location: estate-details.php?id=" . $estateId . "&msg=invalid_input");
exit;
}
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_payment_settings']) && !$isDemo) {
$role = $_SESSION['user_role'] ?? 'guest';
$allowed = in_array(strtolower((string)$role), ['admin','super_admin','estate_manager']);
if ($allowed) {
$estateId = (int)($_POST['estate_id'] ?? 0);
if ($estateId > 0) {
$cur = getEstateConfig($estateId);
$new = $cur;
$new['installment_allowed'] = isset($_POST['installment_allowed']) ? 1 : 0;
$new['excavation_fee_enabled'] = isset($_POST['excavation_fee_enabled']) ? 1 : 0;
$new['infrastructure_fee'] = isset($_POST['infrastructure_fee']) ? (float)$_POST['infrastructure_fee'] : 0.0;
$new['late_penalty_percent'] = isset($_POST['late_penalty_percent']) ? (float)$_POST['late_penalty_percent'] : ($new['late_penalty_percent'] ?? 0.0);
$plans = [];
foreach (['plan3','plan6','plan8'] as $k) { if (isset($_POST[$k])) { $plans[] = (int)substr($k,4); } }
$new['default_plans'] = $plans;
$new['custom_months_default'] = isset($_POST['custom_months_default']) && ctype_digit($_POST['custom_months_default']) ? (int)$_POST['custom_months_default'] : ($new['custom_months_default'] ?? 0);
setEstateConfig($estateId, $new);
auditLogDetailed('estate_config', $estateId, $cur, $new, 'Payment settings updated');
header("Location: estate-details.php?id=" . $estateId . "&msg=updated");
exit;
}
}
}
if ($isDemo) {
$estate = [
'id' => 999,
'name' => 'Aiben Gardens Showcase',
'location' => 'Lekki, Lagos',
'status' => 'active',
'description' => 'Showcase estate demonstrating premium UI and filtering.',
'image' => 'https://picsum.photos/seed/demoestate/1200/600'
];
$properties = [
['id'=>3001,'title'=>'Showcase Unit A','address'=>'Block A','status'=>'available','price'=>45000000],
['id'=>3002,'title'=>'Showcase Unit B','address'=>'Block B','status'=>'reserved','price'=>52000000],
['id'=>3003,'title'=>'Showcase Penthouse','address'=>'Tower 1','status'=>'sold','price'=>150000000],
['id'=>3004,'title'=>'Showcase Villa','address'=>'Villa Row','status'=>'available','price'=>80000000],
['id'=>3005,'title'=>'Showcase Suite','address'=>'Residences','status'=>'sold','price'=>110000000],
['id'=>3006,'title'=>'Showcase Townhouse','address'=>'Court','status'=>'reserved','price'=>60000000],
];
$total_units = count($properties);
$status_counts = ['available'=>3,'reserved'=>2,'sold'=>2];
$available = $status_counts['available'];
$reserved = $status_counts['reserved'];
$sold = $status_counts['sold'];
$sold_percentage = $total_units > 0 ? round(($sold / $total_units) * 100) : 0;
$total_value = array_sum(array_map(function($p){ return $p['price']; }, $properties));
$sold_value = array_sum(array_map(function($p){ return $p['status']==='sold' ? $p['price'] : 0; }, $properties));
} else {
$stmt = $pdo->prepare("SELECT * FROM estates WHERE id = ? $companyFilter");
$stmt->execute([$id]);
$estate = $stmt->fetch();
if (!$estate) {
echo "<div class='container py-5'><div class='alert alert-danger'>Estate not found or access denied.</div></div>";
require 'includes/footer.php';
exit;
}
$stmt = $pdo->prepare("SELECT COUNT(*) FROM properties WHERE estate_id = ? $companyFilter");
$stmt->execute([$id]);
$total_units = $stmt->fetchColumn();
$stmt = $pdo->prepare("SELECT status, COUNT(*) as count FROM properties WHERE estate_id = ? $companyFilter GROUP BY status");
$stmt->execute([$id]);
$status_counts = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$available = $status_counts['available'] ?? 0;
$sold = $status_counts['sold'] ?? 0;
$reserved = $status_counts['reserved'] ?? 0;
$sold_percentage = $total_units > 0 ? round(($sold / $total_units) * 100) : 0;
$stmt = $pdo->prepare("SELECT SUM(price) FROM properties WHERE estate_id = ? $companyFilter");
$stmt->execute([$id]);
$total_value = $stmt->fetchColumn() ?: 0;
$stmt = $pdo->prepare("SELECT SUM(price) FROM properties WHERE estate_id = ? AND status = 'sold' $companyFilter");
$stmt->execute([$id]);
$sold_value = $stmt->fetchColumn() ?: 0;
$stmt = $pdo->prepare("SELECT * FROM properties WHERE estate_id = ? $companyFilter ORDER BY sort_order ASC, id ASC");
$stmt->execute([$id]);
$properties = $stmt->fetchAll();
}
if (!$estate) {
echo "<div class='container py-5'><div class='alert alert-danger'>Estate not found or access denied.</div></div>";
require 'includes/footer.php';
exit;
}
$deleteGuard = function_exists('getEstateDeleteGuard') ? getEstateDeleteGuard((int)$estate['id']) : ['can_delete' => false, 'counts' => [], 'reasons' => []];
$pageMessage = trim((string)($_GET['msg'] ?? ''));
$pageMessageType = 'info';
if ($pageMessage !== '') {
$pageMessageType = in_array($pageMessage, ['forbidden','invalid_input'], true) ? 'danger' : 'success';
if (in_array($pageMessage, ['plots_generated','plot_added','updated'], true)) {
$pageMessageType = 'success';
} elseif (!in_array($pageMessage, ['forbidden','invalid_input'], true)) {
$pageMessageType = 'warning';
}
}
// Fetch Statistics
// Total Units
$stmt = $pdo->prepare("SELECT COUNT(*) FROM properties WHERE estate_id = ? $companyFilter");
$stmt->execute([$id]);
$total_units = $stmt->fetchColumn();
// Status Breakdown
$stmt = $pdo->prepare("SELECT status, COUNT(*) as count FROM properties WHERE estate_id = ? $companyFilter GROUP BY status");
$stmt->execute([$id]);
$status_counts = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$available = $status_counts['available'] ?? 0;
$sold = $status_counts['sold'] ?? 0;
$reserved = $status_counts['reserved'] ?? 0;
$sold_percentage = $total_units > 0 ? round(($sold / $total_units) * 100) : 0;
// Financials (Potential Value vs Realized)
$stmt = $pdo->prepare("SELECT SUM(price) FROM properties WHERE estate_id = ? $companyFilter");
$stmt->execute([$id]);
$total_value = $stmt->fetchColumn() ?: 0;
$stmt = $pdo->prepare("SELECT SUM(price) FROM properties WHERE estate_id = ? AND status = 'sold' $companyFilter");
$stmt->execute([$id]);
$sold_value = $stmt->fetchColumn() ?: 0;
// Fetch Properties List
$stmt = $pdo->prepare("SELECT * FROM properties WHERE estate_id = ? $companyFilter ORDER BY sort_order ASC, id ASC");
$stmt->execute([$id]);
$properties = $stmt->fetchAll();
?>
<div class="container-fluid px-4 py-4">
<!-- HEADER -->
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4 gap-3">
<div>
<div class="d-flex align-items-center gap-2 mb-1">
<h1 class="h3 fw-bold text-primary mb-0"><?= htmlspecialchars($estate['name']) ?></h1>
<span class="badge bg-<?= $estate['status'] == 'active' ? 'success' : 'secondary' ?>-soft text-<?= $estate['status'] == 'active' ? 'success' : 'secondary' ?>">
<?= ucfirst($estate['status']) ?>
</span>
</div>
<p class="text-muted mb-0"><i class="fa-solid fa-location-dot me-1"></i> <?= htmlspecialchars($estate['location']) ?></p>
</div>
<div class="d-flex gap-2 flex-wrap">
<a href="estates.php" class="btn btn-light border shadow-sm">
<i class="fa-solid fa-arrow-left me-2"></i>Back
</a>
<?php if (!isRestrictedMode() && !$isDemo): ?>
<a href="estate-edit.php?id=<?= $estate['id'] ?>" class="btn btn-white border shadow-sm hover-elevate hover-shimmer">
<i class="fa-solid fa-pen me-2"></i>Edit Details
</a>
<a href="estate-layout-management.php?estate_id=<?= $estate['id'] ?>" class="btn btn-white border shadow-sm hover-elevate hover-shimmer">
<i class="fa-solid fa-layer-group me-2"></i>Estate Layout
</a>
<a href="property-add.php?estate_id=<?= $estate['id'] ?>" class="btn btn-primary shadow-sm hover-elevate hover-shimmer">
<i class="fa-solid fa-plus me-2"></i>Add Unit
</a>
<form method="post" onsubmit="return confirm('Delete this estate project? This will only work if there are no linked properties, allocations, payments, or layout records.');">
<input type="hidden" name="action" value="delete_estate">
<button type="submit" class="btn btn-outline-danger shadow-sm" <?= !empty($deleteGuard['can_delete']) ? '' : 'disabled' ?>>
<i class="fa-solid fa-trash me-2"></i>Delete Estate
</button>
</form>
<?php endif; ?>
</div>
</div>
<?php if ($pageMessage !== ''): ?>
<?php
$messageText = $pageMessage;
if ($pageMessage === 'plots_generated') { $messageText = 'Plots generated successfully.'; }
elseif ($pageMessage === 'plot_added') { $messageText = 'Plot added successfully.'; }
elseif ($pageMessage === 'updated') { $messageText = 'Estate payment settings updated successfully.'; }
elseif ($pageMessage === 'invalid_input') { $messageText = 'Please complete all required fields correctly.'; }
elseif ($pageMessage === 'forbidden') { $messageText = 'You do not have permission to perform this action.'; }
?>
<div class="alert alert-<?= htmlspecialchars($pageMessageType) ?>"><?= htmlspecialchars($messageText) ?></div>
<?php endif; ?>
<?php if (!empty($deleteGuard['can_delete'])): ?>
<div class="alert alert-success">This estate can be deleted because no linked properties, allocations, payments, or estate layout records were found.</div>
<?php elseif (!$isDemo && !empty($deleteGuard['reasons'])): ?>
<div class="alert alert-warning">
<div class="fw-semibold mb-1">Delete Estate is currently blocked.</div>
<div class="small mb-2"><?= htmlspecialchars(implode(' ', $deleteGuard['reasons'])) ?></div>
<div class="small text-muted">
Properties: <?= (int)($deleteGuard['counts']['properties'] ?? 0) ?> |
Allocations: <?= (int)($deleteGuard['counts']['allocations'] ?? 0) ?> |
Payments: <?= (int)($deleteGuard['counts']['payments'] ?? 0) ?> |
Layout Versions: <?= (int)($deleteGuard['counts']['layout_versions'] ?? 0) ?> |
Zones: <?= (int)($deleteGuard['counts']['zones'] ?? 0) ?> |
Plots: <?= (int)($deleteGuard['counts']['plots'] ?? 0) ?>
</div>
</div>
<?php endif; ?>
<!-- METRIC CARDS -->
<div class="row g-4 mb-4 animate-stagger">
<!-- Total Units -->
<div class="col-xl-3 col-md-6">
<div class="card metric-card h-100 border-0 shadow-sm animate-card hover-elevate">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<div class="metric-label text-muted small fw-bold text-uppercase">Total Units</div>
<div class="metric-value h2 fw-bold text-dark mb-0"><?= number_format($total_units) ?></div>
</div>
<div class="metric-icon bg-primary-soft text-primary rounded p-2">
<i class="fa-solid fa-layer-group"></i>
</div>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-primary" role="progressbar" style="width: 100%"></div>
</div>
</div>
</div>
</div>
<!-- Availability -->
<div class="col-xl-3 col-md-6">
<div class="card metric-card h-100 border-0 shadow-sm animate-card hover-elevate">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<div class="metric-label text-muted small fw-bold text-uppercase">Available</div>
<div class="metric-value h2 fw-bold text-dark mb-0"><?= number_format($available) ?></div>
</div>
<div class="metric-icon bg-success-soft text-success rounded p-2">
<i class="fa-solid fa-check-circle"></i>
</div>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar" style="width: <?= $total_units > 0 ? ($available/$total_units)*100 : 0 ?>%"></div>
</div>
</div>
</div>
</div>
<!-- Sold / Reserved -->
<div class="col-xl-3 col-md-6">
<div class="card metric-card h-100 border-0 shadow-sm animate-card hover-elevate">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<div class="metric-label text-muted small fw-bold text-uppercase">Sold / Reserved</div>
<div class="metric-value h2 fw-bold text-dark mb-0"><?= number_format($sold + $reserved) ?></div>
</div>
<div class="metric-icon bg-warning-soft text-warning rounded p-2">
<i class="fa-solid fa-handshake"></i>
</div>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: <?= $total_units > 0 ? (($sold+$reserved)/$total_units)*100 : 0 ?>%"></div>
</div>
</div>
</div>
</div>
<!-- Project Value -->
<div class="col-xl-3 col-md-6">
<div class="card metric-card h-100 border-0 shadow-sm animate-card hover-elevate">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<div class="metric-label text-muted small fw-bold text-uppercase">Project Value</div>
<div class="metric-value h2 fw-bold text-dark mb-0"><?= formatCurrency($total_value) ?></div>
</div>
<div class="metric-icon bg-info-soft text-info rounded p-2">
<i class="fa-solid fa-wallet"></i>
</div>
</div>
<div class="small text-muted">
<span class="text-success fw-bold"><?= formatCurrency($sold_value) ?></span> realized
</div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<!-- LEFT COLUMN: OVERVIEW & IMAGE -->
<div class="col-lg-4">
<!-- Image Card -->
<div class="card border-0 shadow-sm mb-4 animate-card hover-elevate">
<?php if (!empty($estate['image'])): ?>
<img src="<?= htmlspecialchars($estate['image']) ?>" class="card-img-top" alt="Estate Layout" style="height: 250px; object-fit: cover;">
<?php else: ?>
<div class="bg-light d-flex align-items-center justify-content-center text-muted card-img-top" style="height: 250px;">
<i class="fa-solid fa-map fa-3x opacity-25"></i>
</div>
<?php endif; ?>
<div class="card-body">
<h6 class="fw-bold text-dark">About Project</h6>
<p class="text-muted small mb-0">
<?= nl2br(htmlspecialchars($estate['description'])) ?>
</p>
</div>
</div>
<?php if (!$isDemo): ?>
<?php $cfg = getEstateConfig((int)$estate['id']); ?>
<div class="card border-0 shadow-sm mb-4 animate-card hover-elevate">
<div class="card-header bg-white border-bottom py-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold">Payment Settings</h6>
<span class="badge bg-light text-dark">Project</span>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="estate_id" value="<?= (int)$estate['id'] ?>">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="installment_allowed" id="installment_allowed" <?= !empty($cfg['installment_allowed']) ? 'checked' : '' ?>>
<label class="form-check-label" for="installment_allowed">Installment Allowed</label>
</div>
<div class="mb-3">
<label class="form-label fw-bold small text-muted">Preset Plans</label>
<div class="d-flex gap-3">
<?php $d = $cfg['default_plans'] ?? []; ?>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="plan3" id="plan3" <?= in_array(3, $d ?? [], true) ? 'checked' : '' ?>>
<label class="form-check-label" for="plan3">3 Months</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="plan6" id="plan6" <?= in_array(6, $d ?? [], true) ? 'checked' : '' ?>>
<label class="form-check-label" for="plan6">6 Months</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="plan8" id="plan8" <?= in_array(8, $d ?? [], true) ? 'checked' : '' ?>>
<label class="form-check-label" for="plan8">8 Months</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Custom Months (default)</label>
<input type="number" class="form-control" name="custom_months_default" min="0" step="1" value="<?= (int)($cfg['custom_months_default'] ?? 0) ?>">
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="excavation_fee_enabled" id="excavation_fee_enabled" <?= !empty($cfg['excavation_fee_enabled']) ? 'checked' : '' ?>>
<label class="form-check-label" for="excavation_fee_enabled">Excavation Fee</label>
</div>
<div class="mb-3">
<label class="form-label">Infrastructure Fee</label>
<div class="input-group">
<span class="input-group-text">₦</span>
<input type="number" class="form-control" name="infrastructure_fee" step="0.01" value="<?= htmlspecialchars((string)($cfg['infrastructure_fee'] ?? 0)) ?>">
</div>
</div>
<div class="mb-3">
<label class="form-label">Late Penalty %</label>
<div class="input-group">
<input type="number" class="form-control" name="late_penalty_percent" step="0.01" min="0" max="100" value="<?= htmlspecialchars((string)($cfg['late_penalty_percent'] ?? 0)) ?>">
<span class="input-group-text">%</span>
</div>
</div>
<div class="d-grid">
<button type="submit" name="save_payment_settings" class="btn btn-primary">Save Settings</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
<?php if (!$isDemo): ?>
<div class="card border-0 shadow-sm mb-4 animate-card hover-elevate">
<div class="card-header bg-white border-bottom py-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold">Plot Bank Management</h6>
<span class="badge bg-light text-dark">Inventory</span>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-12">
<form method="POST" class="row g-2 align-items-end">
<input type="hidden" name="action" value="plots_generate">
<div class="col-3">
<label class="form-label">Block</label>
<input type="text" name="block" class="form-control" placeholder="A" required>
</div>
<div class="col-3">
<label class="form-label">Start No</label>
<input type="number" name="start_no" class="form-control" required>
</div>
<div class="col-3">
<label class="form-label">End No</label>
<input type="number" name="end_no" class="form-control" required>
</div>
<div class="col-3">
<label class="form-label">Size (SQM)</label>
<input type="number" step="0.01" name="size_sqm" class="form-control" required>
</div>
<div class="col-12">
<button class="btn btn-dark w-100" type="submit">Bulk Generate</button>
</div>
</form>
</div>
<div class="col-12">
<form method="POST" class="row g-2 align-items-end">
<input type="hidden" name="action" value="plots_add_single">
<div class="col-4">
<label class="form-label">Block</label>
<input type="text" name="block" class="form-control" required>
</div>
<div class="col-4">
<label class="form-label">Plot No</label>
<input type="text" name="plot_no" class="form-control" required>
</div>
<div class="col-4">
<label class="form-label">Size (SQM)</label>
<input type="number" step="0.01" name="size_sqm" class="form-control" required>
</div>
<div class="col-12">
<button class="btn btn-outline-dark w-100" type="submit">Add Single Plot</button>
</div>
</form>
</div>
<div class="col-12">
<?php
ensurePlotsTable();
$plots = [];
try {
$st = $pdo->prepare("SELECT block, size_sqm, status, COUNT(*) as cnt FROM plots WHERE estate_id = ? GROUP BY block, size_sqm, status ORDER BY block, size_sqm");
$st->execute([(int)$id]);
$plots = $st->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {}
?>
<div class="table-responsive">
<table class="table table-sm">
<thead class="table-light">
<tr>
<th>Block</th>
<th>Size (SQM)</th>
<th>Status</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<?php if (empty($plots)): ?>
<tr><td colspan="4" class="text-center text-muted">No plots</td></tr>
<?php else: foreach ($plots as $p): ?>
<tr>
<td><?= htmlspecialchars($p['block']) ?></td>
<td><?= number_format($p['size_sqm'],2) ?></td>
<td><?= ucfirst($p['status']) ?></td>
<td class="fw-bold"><?= (int)$p['cnt'] ?></td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<!-- Progress Card -->
<div class="card border-0 shadow-sm animate-card hover-elevate">
<div class="card-body">
<h6 class="fw-bold text-dark mb-4">Sales Progress</h6>
<div class="text-center position-relative mb-4">
<div class="display-4 fw-bold text-primary pulse-on-load"><?= $sold_percentage ?>%</div>
<div class="text-muted small text-uppercase tracking-wide">Sold Out</div>
</div>
<div class="d-flex justify-content-between small text-muted mb-2">
<span>0%</span>
<span>50%</span>
<span>100%</span>
</div>
<div class="progress" style="height: 10px;">
<div class="progress-bar bg-primary progress-bar-striped progress-bar-animated" role="progressbar" style="width: <?= $sold_percentage ?>%"></div>
</div>
</div>
</div>
</div>
<!-- RIGHT COLUMN: UNITS INVENTORY -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm h-100 animate-card hover-elevate">
<div class="card-header bg-white border-bottom py-3">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
<h6 class="mb-0 fw-bold">Property Units Inventory</h6>
<div class="d-flex flex-wrap align-items-center gap-2">
<ul class="nav nav-pills" id="statusFilters">
<li class="nav-item"><a class="nav-link active" href="#" data-filter="all">All</a></li>
<li class="nav-item"><a class="nav-link" href="#" data-filter="available">Available</a></li>
<li class="nav-item"><a class="nav-link" href="#" data-filter="reserved">Reserved</a></li>
<li class="nav-item"><a class="nav-link" href="#" data-filter="sold">Sold</a></li>
</ul>
<div class="input-group input-group-sm w-auto">
<span class="input-group-text bg-light border-end-0"><i class="fa-solid fa-search"></i></span>
<input type="text" class="form-control bg-light border-start-0" placeholder="Search units..." id="unitsSearch">
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0 table-appear">
<thead class="bg-light">
<tr>
<th class="ps-4">Unit / Plot</th>
<th>Type</th>
<th>Price</th>
<th>Status</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($properties)): ?>
<tr>
<td colspan="5" class="text-center py-5 text-muted">
<i class="fa-solid fa-box-open fa-2x mb-3 opacity-25"></i>
<p class="mb-0">No units added yet.</p>
</td>
</tr>
<?php else: ?>
<?php foreach ($properties as $prop): ?>
<tr data-status="<?= htmlspecialchars($prop['status']) ?>">
<td class="ps-4">
<div class="fw-bold text-dark"><?= htmlspecialchars($prop['title']) ?></div>
<div class="small text-muted"><?= htmlspecialchars($prop['address'] ?? '') ?></div>
</td>
<td>
<span class="badge badge-soft bg-light text-dark border">
<?= ucfirst($prop['type'] ?? 'Plot') ?>
</span>
</td>
<td class="fw-bold text-dark">
<?= formatCurrency($prop['price']) ?>
</td>
<td>
<?php
$statusClass = match($prop['status']) {
'available' => 'success',
'sold' => 'danger',
'reserved' => 'warning',
default => 'secondary'
};
?>
<span class="badge badge-soft bg-<?= $statusClass ?>-soft text-<?= $statusClass ?>">
<?= ucfirst($prop['status']) ?>
</span>
</td>
<td class="text-end pe-4">
<div class="dropdown">
<button class="btn btn-sm btn-light border hover-elevate" type="button" data-bs-toggle="dropdown">
<i class="fa-solid fa-ellipsis-vertical"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end border-0 shadow-lg">
<?php if (!isRestrictedMode() && !$isDemo): ?>
<li><a class="dropdown-item" href="property-edit.php?id=<?= $prop['id'] ?>"><i class="fa-solid fa-pen-to-square me-2 text-muted"></i>Edit</a></li>
<li><a class="dropdown-item" href="allocation-add.php?property_id=<?= $prop['id'] ?>"><i class="fa-solid fa-user-check me-2 text-muted"></i>Allocate</a></li>
<?php endif; ?>
<li><hr class="dropdown-divider"></li>
<?php if (!isRestrictedMode() && !$isDemo): ?>
<li><a class="dropdown-item text-danger" href="property-delete.php?id=<?= $prop['id'] ?>" onclick="return confirm('Are you sure?')"><i class="fa-solid fa-trash me-2"></i>Delete</a></li>
<?php endif; ?>
</ul>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<?php require 'includes/footer.php'; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
var searchInput = document.getElementById('unitsSearch');
var filterNav = document.getElementById('statusFilters');
var tbody = document.querySelector('.table-appear tbody');
var filter = 'all';
function applyFilters() {
var q = (searchInput && searchInput.value ? searchInput.value.toLowerCase() : '');
var rows = tbody ? tbody.querySelectorAll('tr') : [];
rows.forEach(function(row) {
var status = row.getAttribute('data-status') || '';
var text = row.textContent.toLowerCase();
var matchStatus = filter === 'all' || status === filter;
var matchSearch = !q || text.indexOf(q) !== -1;
row.style.display = (matchStatus && matchSearch) ? '' : 'none';
});
}
if (searchInput) {
searchInput.addEventListener('input', applyFilters);
}
if (filterNav) {
filterNav.querySelectorAll('.nav-link').forEach(function(link) {
link.addEventListener('click', function(e) {
e.preventDefault();
filterNav.querySelectorAll('.nav-link').forEach(function(l) { l.classList.remove('active'); });
this.classList.add('active');
filter = this.getAttribute('data-filter') || 'all';
applyFilters();
});
});
}
});
</script>