37748-vm/job_detail.php
2026-01-23 15:54:08 +00:00

393 lines
21 KiB
PHP

<?php
require_once 'includes/helpers.php';
$user = getCurrentAppUser();
$company_id = get_current_company_id();
$job_id = $_GET['id'] ?? null;
if (!$job_id || !$user || !$company_id) {
header("Location: index.php");
exit;
}
// Fetch Job
$stmt = db()->prepare("SELECT j.*, s.name as status_name, c.name as client_name
FROM jobs j
LEFT JOIN job_statuses s ON j.status_id = s.id
LEFT JOIN clients c ON j.client_id = c.id
WHERE j.id = ? AND j.company_id = ?");
$stmt->execute([$job_id, $company_id]);
$job = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$job) {
die("Job not found.");
}
// Ensure all required folders for this company are created for this job
ensureRequiredJobFoldersExist($job_id, $company_id);
// Handle POST requests for File & Folder Management
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'toggle_approved') {
$new_val = $job['works_approved'] ? 0 : 1;
$stmt = db()->prepare("UPDATE jobs SET works_approved = ? WHERE id = ?");
$stmt->execute([$new_val, $job_id]);
logActivity($job_id, 'works_approved_toggle', 'works_approved', $job['works_approved'] ? 'True' : 'False', $new_val ? 'True' : 'False');
header("Location: job_detail.php?id=" . $job_id);
exit;
} elseif ($action === 'add_folder') {
$folder_name = trim($_POST['folder_name'] ?? '');
if (!empty($folder_name)) {
$result = createJobFolder($job_id, $company_id, $folder_name, TRUE);
if ($result['success']) {
// Log activity (already handled inside createJobFolder via ensureRequiredJobFoldersExist)
header("Location: job_detail.php?id=" . $job_id . "&message=Folder '" . urlencode($folder_name) . "' created successfully.&message_type=success");
exit;
} else {
$error = $result['message'] ?? 'Failed to create folder.';
}
} else {
$error = 'Folder name cannot be empty.';
}
} elseif ($action === 'delete_folder') {
$job_job_folder_id = (int)$_POST['job_job_folder_id'];
$result = deleteJobFolder($job_job_folder_id, $job_id, $company_id);
if ($result['success']) {
header("Location: job_detail.php?id=" . $job_id . "&message=Folder deleted successfully.&message_type=success");
exit;
} else {
$error = $result['message'] ?? 'Failed to delete folder.';
}
} elseif ($action === 'upload_file') {
$job_job_folder_id = (int)$_POST['job_job_folder_id'];
if (isset($_FILES['file_upload']) && $_FILES['file_upload']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['file_upload']['tmp_name'];
$file_name = basename($_FILES['file_upload']['name']);
$file_size = $_FILES['file_upload']['size'];
$file_type = $_FILES['file_upload']['type'];
// Define upload directory: uploads/company_id/job_id/job_job_folder_id/
$upload_dir = __DIR__ . "/uploads/{$company_id}/{$job_id}/{$job_job_folder_id}/";
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0775, true);
}
$dest_path = $upload_dir . $file_name;
if (move_uploaded_file($file_tmp_path, $dest_path)) {
$result = addJobFile($job_id, $job_job_folder_id, $company_id, $user['id'], $file_name, $dest_path, $file_type, $file_size);
if ($result['success']) {
header("Location: job_detail.php?id=" . $job_id . "&message=File '" . urlencode($file_name) . "' uploaded successfully.&message_type=success");
exit;
} else {
// If DB insert fails, try to remove the uploaded file
if (file_exists($dest_path)) { unlink($dest_path); }
$error = 'Failed to record file in database.';
}
} else {
$error = 'Failed to move uploaded file.';
}
} else {
$error = 'File upload error or no file selected.';
}
} elseif ($action === 'delete_file') {
$file_id = (int)$_POST['file_id'];
$result = deleteJobFile($file_id, $job_id, $company_id);
if ($result['success']) {
header("Location: job_detail.php?id=" . $job_id . "&message=File deleted successfully.&message_type=success");
exit;
} else {
$error = $result['message'] ?? 'Failed to delete file.';
}
}
}
// Fetch Logs
$logStmt = db()->prepare("SELECT * FROM activity_logs WHERE job_id = ? ORDER BY created_at DESC");
$logStmt->execute([$job_id]);
$logs = $logStmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch all job folders (required and custom)
$jobFolders = getJobFolders($job_id, $company_id);
// Get messages from URL for display
$message = $_GET['message'] ?? '';
$message_type = $_GET['message_type'] ?? '';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Detail - <?php echo htmlspecialchars($job['job_ref']); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
<style>
.folder-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
border: 1px solid #E5E7EB;
border-radius: 4px;
margin-bottom: 8px;
background-color: #F9FAFB;
}
.folder-item.required { background-color: #FFFBEB; border-color: #FCD34D; }
.folder-item .folder-name {
font-weight: 500;
color: #111827;
}
.folder-item .folder-actions button {
margin-left: 8px;
}
.message { padding: 15px; border-radius: 4px; margin-bottom: 20px; }
.message.success { background-color: #D1FAE5; color: #065F46; border: 1px solid #34D399; }
.message.error { background-color: #FEE2E2; color: #991B1B; border: 1px solid #F87171; }
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg py-3">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">Repairs Pro</a>
<div class="d-flex align-items-center">
<a href="index.php" class="btn btn-light btn-sm me-2">Back to Dashboard</a>
</div>
</div>
</nav>
<main class="container py-5">
<?php if ($message): ?>
<div class="message <?php echo $message_type; ?>">
<?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<div class="row">
<div class="col-lg-8">
<div class="card p-4 mb-4">
<div class="d-flex justify-content-between align-items-start mb-4">
<div>
<span class="text-secondary text-uppercase small fw-bold">Job Reference</span>
<h1 class="h3 fw-bold"><?php echo htmlspecialchars($job['job_ref']); ?></h1>
</div>
<div class="text-end">
<span class="badge bg-light text-dark border p-2 px-3"><?php echo htmlspecialchars($job['status_name']); ?></span>
</div>
</div>
<div class="row g-4">
<div class="col-md-6">
<label class="text-secondary small">Client</label>
<div class="fw-medium"><?php echo htmlspecialchars($job['client_name']); ?></div>
</div>
<div class="col-md-6">
<label class="text-secondary small">Created At</label>
<div class="fw-medium"><?php echo date('d M Y, H:i', strtotime($job['created_at'])); ?></div>
</div>
<div class="col-12">
<label class="text-secondary small">Address</label>
<div class="fw-medium"><?php echo htmlspecialchars($job['address_1'] ?: 'N/A'); ?></div>
</div>
<div class="col-12">
<label class="text-secondary small">Description</label>
<div class="p-3 bg-light border-start border-primary border-4 rounded-1">
<?php echo nl2br(htmlspecialchars($job['description'] ?: 'No description provided.')); ?>
</div>
</div>
</div>
<hr class="my-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h5 class="m-0 fw-bold">Works Approved</h5>
<p class="text-secondary small m-0">Toggle this when the scope of work is confirmed.</p>
</div>
<form method="POST">
<input type="hidden" name="action" value="toggle_approved">
<button type="submit" class="btn <?php echo $job['works_approved'] ? 'btn-success' : 'btn-outline-secondary'; ?>">
<?php echo $job['works_approved'] ? 'Approved' : 'Mark as Approved'; ?>
</button>
</form>
</div>
<hr class="my-4">
<!-- Job Folders Section -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="m-0 fw-bold">Job Folders</h5>
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addFolderModal">Add Custom Folder</button>
</div>
<div class="folder-list">
<?php if (!empty($jobFolders)): ?>
<?php foreach ($jobFolders as $folder): ?>
<div class="folder-item <?php echo !$folder['is_custom'] ? 'required' : ''; ?>">
<span class="folder-name">
<?php echo htmlspecialchars($folder['name']); ?>
(<?php echo $folder['file_count']; ?> files)
<?php if (!$folder['is_custom']): ?>
<span class="badge bg-warning text-dark ms-2">Required</span>
<?php endif; ?>
</span>
<div class="folder-actions">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#uploadFileModal" data-job-job-folder-id="<?php echo $folder['id']; ?>" data-folder-name="<?php echo htmlspecialchars($folder['name']); ?>">Upload File</button>
<?php if ($folder['is_custom']): ?>
<form method="POST" class="d-inline-block" onsubmit="return confirm('Are you sure you want to delete this folder? This action cannot be undone.');">
<input type="hidden" name="action" value="delete_folder">
<input type="hidden" name="job_job_folder_id" value="<?php echo $folder['id']; ?>">
<button type="submit" class="btn btn-sm btn-outline-danger" <?php echo ($folder['file_count'] > 0) ? 'disabled title="Folder must be empty to delete"' : ''; ?>>Delete</button>
</form>
<?php endif; ?>
</div>
</div>
<!-- Display Files within this folder -->
<div class="files-list ms-4 mb-3">
<?php $files = getFilesInJobFolder($folder['id'], $company_id); ?>
<?php if (!empty($files)): ?>
<?php foreach ($files as $file): ?>
<div class="d-flex justify-content-between align-items-center mb-1 ps-2 py-1 border-start border-secondary">
<span><a href="<?php echo htmlspecialchars($file['filepath']); ?>" target="_blank"><?php echo htmlspecialchars($file['filename']); ?></a> (<?php echo round($file['size'] / 1024, 2); ?> KB)</span>
<form method="POST" class="d-inline-block" onsubmit="return confirm('Are you sure you want to delete this file? This action cannot be undone.');">
<input type="hidden" name="action" value="delete_file">
<input type="hidden" name="file_id" value="<?php echo $file['id']; ?>">
<button type="submit" class="btn btn-sm btn-outline-danger">Delete File</button>
</form>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="text-muted small ms-2">No files in this folder.</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="alert alert-info">No folders set up for this job yet.</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header bg-white py-3">
<h5 class="m-0 fw-bold">Activity Log</h5>
</div>
<div class="activity-log">
<?php foreach ($logs as $log): ?>
<div class="log-entry">
<div class="d-flex justify-content-between mb-1">
<span class="fw-bold"><?php echo htmlspecialchars($log['user_name']); ?></span>
<span class="text-secondary x-small"><?php echo date('H:i', strtotime($log['created_at'])); ?></span>
</div>
<div class="text-primary small mb-1">
<?php
switch($log['event_type']) {
case 'job_created': echo "Created the job"; break;
case 'works_approved_toggle': echo "Updated 'Works Approved' status"; break;
case 'folder_deleted': echo "Deleted folder: ". htmlspecialchars($log['old_value']); break;
case 'file_uploaded': echo "Uploaded file: ". htmlspecialchars($log['new_value']); break;
case 'file_deleted': echo "Deleted file: ". htmlspecialchars($log['old_value']); break;
default: echo htmlspecialchars($log['event_type']);
}
?>
</div>
<?php if ($log['field_name']): ?>
<div class="text-secondary" style="font-size: 0.75rem;">
<?php if ($log['old_value'] !== null): ?>
<span class="text-decoration-line-through"><?php echo htmlspecialchars($log['old_value']); ?></span>
&rarr;
<?php endif; ?>
<span class="fw-medium"><?php echo htmlspecialchars($log['new_value']); ?></span>
</div>
<?php endif; ?>
<div class="text-muted mt-1" style="font-size: 0.7rem;">
<?php echo date('d M Y', strtotime($log['created_at'])); ?>
</div>
</div>
<?php endforeach; if (empty($logs)): ?>
<div class="p-4 text-center text-secondary">No activity yet.</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</main>
<!-- Modals -->
<!-- Add Folder Modal -->
<div class="modal fade" id="addFolderModal" tabindex="-1" aria-labelledby="addFolderModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form class="modal-content" method="POST">
<input type="hidden" name="action" value="add_folder">
<div class="modal-header">
<h5 class="modal-title" id="addFolderModalLabel">Add Custom Folder</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="folderName" class="form-label">Folder Name</label>
<input type="text" class="form-control" id="folderName" name="folder_name" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create Folder</button>
</div>
</form>
</div>
</div>
<!-- Upload File Modal -->
<div class="modal fade" id="uploadFileModal" tabindex="-1" aria-labelledby="uploadFileModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form class="modal-content" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="upload_file">
<input type="hidden" name="job_job_folder_id" id="uploadFolderId">
<div class="modal-header">
<h5 class="modal-title" id="uploadFileModalLabel">Upload File to <span id="uploadFolderName"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="fileUpload" class="form-label">Select File</label>
<input type="file" class="form-control" id="fileUpload" name="file_upload" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Populate folder ID and name in upload file modal
var uploadFileModal = document.getElementById('uploadFileModal');
uploadFileModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget;
var jobJobFolderId = button.getAttribute('data-job-job-folder-id');
var folderName = button.getAttribute('data-folder-name');
var modalFolderIdInput = uploadFileModal.querySelector('#uploadFolderId');
var modalFolderNameSpan = uploadFileModal.querySelector('#uploadFolderName');
modalFolderIdInput.value = jobJobFolderId;
modalFolderNameSpan.textContent = folderName;
});
</script>
</body>
</html>