| 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
include 'includes/header.php';
$roleRaw = $_SESSION['user_role'] ?? '';
$role = strtolower((string)$roleRaw);
$userId = (int)($_SESSION['user_id'] ?? 0);
$isFinanceStaff = in_array($role, ['finance', 'finance_officer'], true);
$isFinanceManager = ($role === 'finance_manager');
// Check Permissions
if (!in_array($_SESSION['user_role'], ['admin', 'super_admin', 'operations', 'operations_manager', 'estate_manager', 'maintenance_staff', 'finance', 'finance_officer', 'finance_manager'])) {
echo "<div class='alert alert-danger m-4'>Access Denied. You do not have permission to view this page.</div>";
include 'includes/footer.php';
exit;
}
// Handle Delete
if (isset($_GET['action'], $_GET['id']) && $_GET['action'] === 'delete') {
try {
$stmt = $pdo->prepare("DELETE FROM maintenance_requests WHERE id = ?");
$stmt->execute([(int)$_GET['id']]);
header("Location: maintenance.php?notice=" . urlencode("Request deleted") . "&type=success");
exit;
} catch (PDOException $e) {
header("Location: maintenance.php?notice=" . urlencode("Delete failed") . "&type=danger");
exit;
}
}
// Handle Status Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'update_status') {
try {
$assigned_to = !empty($_POST['assigned_to']) ? $_POST['assigned_to'] : null;
$scheduled_date = !empty($_POST['scheduled_date']) ? $_POST['scheduled_date'] : null;
$requestId = (int)($_POST['request_id'] ?? 0);
$newStatus = (string)($_POST['status'] ?? '');
$hasCostInput = array_key_exists('cost', $_POST) && $_POST['cost'] !== '';
$postedCost = $hasCostInput ? (float)$_POST['cost'] : null;
$currentCost = null;
if ($requestId > 0) {
$cur = $pdo->prepare("SELECT cost FROM maintenance_requests WHERE id = ? LIMIT 1");
$cur->execute([$requestId]);
$cc = $cur->fetchColumn();
$currentCost = ($cc !== false && $cc !== null) ? (float)$cc : null;
}
$costChanged = $hasCostInput && ((float)$postedCost !== (float)($currentCost ?? 0));
if ($costChanged && !$isFinanceStaff) {
header("Location: maintenance.php?notice=" . urlencode("Access Denied: Finance Controlled") . "&type=danger");
exit;
}
if (strtolower(trim($newStatus)) === 'completed' && !$isFinanceManager) {
header("Location: maintenance.php?notice=" . urlencode("Access Denied: Finance Manager approval required") . "&type=danger");
exit;
}
$sets = ["status = ?", "assigned_to = ?", "scheduled_date = ?"];
$vals = [$newStatus, $assigned_to, $scheduled_date];
if ($costChanged) {
$sets[] = "cost = ?";
$vals[] = $postedCost;
if (function_exists('tableHasColumn') && tableHasColumn('maintenance_requests', 'recorded_by_user_id')) {
$sets[] = "recorded_by_user_id = ?";
$vals[] = $userId;
}
if (function_exists('tableHasColumn') && tableHasColumn('maintenance_requests', 'recorded_by_role')) {
$sets[] = "recorded_by_role = ?";
$vals[] = 'finance';
}
}
$vals[] = $requestId;
$stmt = $pdo->prepare("UPDATE maintenance_requests SET " . implode(', ', $sets) . " WHERE id = ?");
$stmt->execute($vals);
header("Location: maintenance.php?notice=" . urlencode("Request updated successfully") . "&type=success");
exit;
} catch (PDOException $e) {
header("Location: maintenance.php?notice=" . urlencode("Error updating request") . "&type=danger");
exit;
}
} elseif ($_POST['action'] === 'create_request') {
try {
$stmt = $pdo->prepare("INSERT INTO maintenance_requests (property_id, tenant_id, title, issue_type, description, priority, status) VALUES (?, ?, ?, ?, ?, ?, 'open')");
$stmt->execute([
$_POST['property_id'],
$_POST['tenant_id'],
$_POST['title'],
$_POST['issue_type'],
$_POST['description'],
$_POST['priority']
]);
header("Location: maintenance.php?notice=" . urlencode("Maintenance request created successfully") . "&type=success");
exit;
} catch (PDOException $e) {
header("Location: maintenance.php?notice=" . urlencode("Error creating request") . "&type=danger");
exit;
}
}
header("Location: maintenance.php");
exit;
}
// KPI Stats
try {
$companyId = getCurrentCompanyId();
$companyFilter = $companyId ? " WHERE company_id = {$companyId}" : "";
$kpis = [
'total' => $pdo->query("SELECT COUNT(*) FROM maintenance_requests" . $companyFilter)->fetchColumn(),
'open' => $pdo->query("SELECT COUNT(*) FROM maintenance_requests" . $companyFilter . ($companyFilter ? " AND" : " WHERE") . " status = 'open'")->fetchColumn(),
'in_progress' => $pdo->query("SELECT COUNT(*) FROM maintenance_requests" . $companyFilter . ($companyFilter ? " AND" : " WHERE") . " status = 'in_progress'")->fetchColumn(),
'urgent' => $pdo->query("SELECT COUNT(*) FROM maintenance_requests" . $companyFilter . ($companyFilter ? " AND" : " WHERE") . " priority = 'urgent' AND status != 'closed'")->fetchColumn(),
];
} catch (Exception $e) {
$kpis = array_fill_keys(['total', 'open', 'in_progress', 'urgent'], 0);
}
// Fetch Requests
$search = $_GET['search'] ?? '';
$status_filter = $_GET['status'] ?? '';
$priority_filter = $_GET['priority'] ?? '';
$sql = "SELECT m.*,
p.title as property_title,
u.name as tenant_name,
s.name as assigned_name
FROM maintenance_requests m
LEFT JOIN properties p ON m.property_id = p.id
LEFT JOIN users u ON m.tenant_id = u.id
LEFT JOIN users s ON m.assigned_to = s.id
WHERE 1=1";
$params = [];
if (!isset($companyId)) {
$companyId = getCurrentCompanyId();
}
if ($companyId) {
$sql .= " AND m.company_id = ?";
$params[] = $companyId;
}
if ($search) {
$sql .= " AND (m.title LIKE ? OR p.title LIKE ? OR u.name LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
if ($status_filter) {
$sql .= " AND m.status = ?";
$params[] = $status_filter;
}
if ($priority_filter) {
$sql .= " AND m.priority = ?";
$params[] = $priority_filter;
}
$sql .= " ORDER BY m.created_at DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$requests = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch Staff for Assignment
if ($companyId) {
$staff_stmt = $pdo->prepare("SELECT id, name FROM users WHERE role IN ('operations', 'operations_officer', 'maintenance_staff', 'contractor') AND company_id = ? ORDER BY name");
$staff_stmt->execute([$companyId]);
$staff_members = $staff_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$staff_stmt = $pdo->query("SELECT id, name FROM users WHERE role IN ('operations', 'operations_officer', 'maintenance_staff', 'contractor') ORDER BY name");
$staff_members = $staff_stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Fetch Properties & Tenants for New Request
if ($companyId) {
$props_stmt = $pdo->prepare("SELECT id, title FROM properties WHERE company_id = ? ORDER BY title");
$props_stmt->execute([$companyId]);
$properties = $props_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$props_stmt = $pdo->query("SELECT id, title FROM properties ORDER BY title");
$properties = $props_stmt->fetchAll(PDO::FETCH_ASSOC);
}
if ($companyId) {
$tenants_stmt = $pdo->prepare("SELECT id, name FROM users WHERE role = 'client' AND company_id = ? ORDER BY name");
$tenants_stmt->execute([$companyId]);
$tenants = $tenants_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$tenants_stmt = $pdo->query("SELECT id, name FROM users WHERE role = 'client' ORDER BY name");
$tenants = $tenants_stmt->fetchAll(PDO::FETCH_ASSOC);
}
?>
<div class="main-content p-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold text-navy"><i class="fas fa-screwdriver-wrench me-2"></i>Maintenance Requests</h2>
<p class="text-muted mb-0">Track and manage property maintenance issues.</p>
</div>
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createRequestModal">
<i class="fas fa-plus me-2"></i>New Request
</button>
</div>
<!-- KPI Strip -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100 bg-navy text-white">
<div class="card-body d-flex align-items-center">
<div class="rounded-circle bg-white bg-opacity-25 p-3 me-3">
<i class="fas fa-clipboard-list fa-2x"></i>
</div>
<div>
<h6 class="card-subtitle text-white-50 mb-1">Total Requests</h6>
<h2 class="card-title mb-0"><?= number_format($kpis['total']) ?></h2>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body d-flex align-items-center text-danger">
<div class="rounded-circle bg-danger bg-opacity-10 p-3 me-3">
<i class="fas fa-exclamation-circle fa-2x"></i>
</div>
<div>
<h6 class="card-subtitle text-muted mb-1">Urgent Issues</h6>
<h2 class="card-title mb-0"><?= number_format($kpis['urgent']) ?></h2>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body d-flex align-items-center text-primary">
<div class="rounded-circle bg-primary bg-opacity-10 p-3 me-3">
<i class="fas fa-folder-open fa-2x"></i>
</div>
<div>
<h6 class="card-subtitle text-muted mb-1">Open Requests</h6>
<h2 class="card-title mb-0"><?= number_format($kpis['open']) ?></h2>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body d-flex align-items-center text-warning">
<div class="rounded-circle bg-warning bg-opacity-10 p-3 me-3">
<i class="fas fa-tools fa-2x"></i>
</div>
<div>
<h6 class="card-subtitle text-muted mb-1">In Progress</h6>
<h2 class="card-title mb-0"><?= number_format($kpis['in_progress']) ?></h2>
</div>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="card shadow-sm mb-4 border-0">
<div class="card-body">
<form method="GET" class="row g-3">
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="fas fa-search text-muted"></i></span>
<input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="Search by title, property, or tenant..." value="<?= htmlspecialchars($search) ?>">
</div>
</div>
<div class="col-md-3">
<select name="status" class="form-select" onchange="this.form.submit()">
<option value="">All Statuses</option>
<option value="open" <?= $status_filter == 'open' ? 'selected' : '' ?>>Open</option>
<option value="in_progress" <?= $status_filter == 'in_progress' ? 'selected' : '' ?>>In Progress</option>
<option value="resolved" <?= $status_filter == 'resolved' ? 'selected' : '' ?>>Resolved</option>
<option value="closed" <?= $status_filter == 'closed' ? 'selected' : '' ?>>Closed</option>
<option value="completed" <?= $status_filter == 'completed' ? 'selected' : '' ?>>Completed</option>
</select>
</div>
<div class="col-md-3">
<select name="priority" class="form-select" onchange="this.form.submit()">
<option value="">All Priorities</option>
<option value="low" <?= $priority_filter == 'low' ? 'selected' : '' ?>>Low</option>
<option value="medium" <?= $priority_filter == 'medium' ? 'selected' : '' ?>>Medium</option>
<option value="high" <?= $priority_filter == 'high' ? 'selected' : '' ?>>High</option>
<option value="urgent" <?= $priority_filter == 'urgent' ? 'selected' : '' ?>>Urgent</option>
</select>
</div>
<div class="col-md-2 d-grid">
<a href="maintenance.php" class="btn btn-light border">Reset</a>
</div>
</form>
</div>
</div>
<!-- Requests List -->
<div class="card shadow-sm border-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Request</th>
<th>Property</th>
<th>Priority</th>
<th>Status</th>
<th>Assigned To</th>
<th>Cost</th>
<th>Created</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($requests)): ?>
<tr>
<td colspan="8" class="text-center py-5 text-muted">
<i class="fas fa-clipboard-check fa-3x mb-3 opacity-50"></i>
<p>No maintenance requests found.</p>
</td>
</tr>
<?php else: ?>
<?php foreach ($requests as $req):
$statusClass = match($req['status']) {
'open' => 'bg-info bg-opacity-10 text-info',
'in_progress' => 'bg-warning bg-opacity-10 text-warning',
'resolved' => 'bg-success bg-opacity-10 text-success',
'closed' => 'bg-secondary bg-opacity-10 text-secondary',
default => 'bg-light text-dark'
};
$priorityClass = match($req['priority']) {
'urgent' => 'text-danger fw-bold',
'high' => 'text-danger',
'medium' => 'text-warning',
'low' => 'text-success',
default => 'text-muted'
};
?>
<tr style="cursor: pointer;" onclick="openRequestDetails(<?= $req['id'] ?>)">
<td class="ps-4">
<div class="fw-bold text-navy"><?= htmlspecialchars($req['title'] ?: $req['issue_type']) ?></div>
<div class="small text-muted text-truncate" style="max-width: 200px;"><?= htmlspecialchars($req['description']) ?></div>
</td>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm rounded bg-light text-primary d-flex align-items-center justify-content-center me-2">
<i class="fas fa-building"></i>
</div>
<div>
<div class="fw-bold small"><?= htmlspecialchars($req['property_title']) ?></div>
<div class="small text-muted">Ten: <?= htmlspecialchars($req['tenant_name']) ?></div>
</div>
</div>
</td>
<td>
<span class="<?= $priorityClass ?> text-uppercase small">
<i class="fas fa-circle fa-xs me-1"></i><?= $req['priority'] ?>
</span>
</td>
<td>
<?= getStatusBadge($req['status']) ?>
</td>
<td>
<?php if($req['assigned_name']): ?>
<div class="d-flex align-items-center">
<div class="avatar-xs rounded-circle bg-primary text-white d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px; font-size: 0.7rem;">
<?= strtoupper(substr($req['assigned_name'], 0, 1)) ?>
</div>
<span class="small"><?= htmlspecialchars($req['assigned_name']) ?></span>
</div>
<?php else: ?>
<span class="badge bg-light text-muted border fw-normal">Unassigned</span>
<?php endif; ?>
</td>
<td>
<span class="text-muted fw-bold"><?= $req['cost'] > 0 ? '$' . number_format($req['cost'], 2) : '-' ?></span>
</td>
<td class="small text-muted">
<?= date('M d, Y', strtotime($req['created_at'])) ?>
</td>
<td class="text-end pe-4" onclick="event.stopPropagation()">
<div class="dropdown">
<button class="btn btn-sm btn-light border-0" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
<li><a class="dropdown-item" href="#" onclick="openRequestDetails(<?= $req['id'] ?>)"><i class="fas fa-eye me-2 text-primary"></i>View Details</a></li>
<li><a class="dropdown-item text-danger" href="maintenance.php?action=delete&id=<?= $req['id'] ?>" onclick="return confirm('Delete this request?')"><i class="fas fa-trash-alt me-2"></i>Delete</a></li>
</ul>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Side Drawer -->
<div class="drawer-backdrop" id="drawerBackdrop"></div>
<div class="side-drawer shadow-lg" id="sideDrawer">
<div class="drawer-header border-bottom p-3 d-flex justify-content-between align-items-center bg-white sticky-top">
<h5 class="mb-0 fw-bold"><i class="fas fa-tools me-2 text-primary"></i>Request Details</h5>
<button type="button" class="btn-close" onclick="closeDrawer()"></button>
</div>
<div class="drawer-content p-0" id="drawerContent">
<div class="d-flex justify-content-center align-items-center h-100">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
<!-- Create Request Modal -->
<div class="modal fade" id="createRequestModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<input type="hidden" name="action" value="create_request">
<div class="modal-header">
<h5 class="modal-title">New Maintenance Request</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Title</label>
<input type="text" name="title" class="form-control" required placeholder="e.g. Leaky Faucet in Kitchen">
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label">Property</label>
<select name="property_id" class="form-select" required>
<option value="">Select Property</option>
<?php foreach ($properties as $prop): ?>
<option value="<?= $prop['id'] ?>"><?= htmlspecialchars($prop['title']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Tenant (Reporter)</label>
<select name="tenant_id" class="form-select" required>
<option value="">Select Tenant</option>
<?php foreach ($tenants as $tenant): ?>
<option value="<?= $tenant['id'] ?>"><?= htmlspecialchars($tenant['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label">Category</label>
<select name="issue_type" class="form-select" required>
<option value="Plumbing">Plumbing</option>
<option value="Electrical">Electrical</option>
<option value="HVAC">HVAC</option>
<option value="Structural">Structural</option>
<option value="Appliance">Appliance</option>
<option value="Other">Other</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Priority</label>
<select name="priority" class="form-select" required>
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3" required></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create Request</button>
</div>
</form>
</div>
</div>
<script>
function openRequestDetails(id) {
document.getElementById('drawerBackdrop').classList.add('show');
document.getElementById('sideDrawer').classList.add('open');
document.body.style.overflow = 'hidden';
fetch(`ajax_get_maintenance_details.php?id=${id}`)
.then(response => response.text())
.then(html => {
document.getElementById('drawerContent').innerHTML = html;
})
.catch(err => {
document.getElementById('drawerContent').innerHTML = '<div class="alert alert-danger m-3">Failed to load details.</div>';
});
}
function closeDrawer() {
document.getElementById('drawerBackdrop').classList.remove('show');
document.getElementById('sideDrawer').classList.remove('open');
document.body.style.overflow = '';
}
document.getElementById('drawerBackdrop').addEventListener('click', closeDrawer);
try {
var sp = new URLSearchParams(window.location.search);
var vid = sp.get('view_id');
if (vid && /^\d+$/.test(vid)) {
openRequestDetails(parseInt(vid, 10));
}
} catch (e) {}
</script>
<?php include 'includes/footer.php'; ?>