reports
This commit is contained in:
parent
10ea0441d5
commit
444cd73cc6
@ -157,3 +157,60 @@ body {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Calendar Styles */
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 1px;
|
||||
background-color: #e2e8f0;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 0.375rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
background-color: #ffffff;
|
||||
min-height: 100px;
|
||||
padding: 0.75rem;
|
||||
position: relative;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.calendar-day:hover {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.calendar-header-day {
|
||||
background-color: #f1f5f9;
|
||||
text-align: center;
|
||||
padding: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.day-number {
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.day-hours {
|
||||
position: absolute;
|
||||
bottom: 0.75rem;
|
||||
right: 0.75rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.other-month {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.other-month .day-number {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
7
db/migrations/005_enhanced_projects.sql
Normal file
7
db/migrations/005_enhanced_projects.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- Add new fields to projects table
|
||||
ALTER TABLE projects
|
||||
ADD COLUMN owner_id INT NULL AFTER tenant_id,
|
||||
ADD COLUMN estimated_completion_date DATE NULL AFTER start_date,
|
||||
ADD COLUMN type ENUM('Internal', 'SRED') NOT NULL DEFAULT 'Internal' AFTER code,
|
||||
ADD COLUMN estimated_hours DECIMAL(10,2) NOT NULL DEFAULT 0.00 AFTER type,
|
||||
ADD CONSTRAINT fk_project_owner FOREIGN KEY (owner_id) REFERENCES employees(id) ON DELETE SET NULL;
|
||||
256
index.php
256
index.php
@ -87,10 +87,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_project'])) {
|
||||
$name = $_POST['name'] ?? '';
|
||||
$code = $_POST['code'] ?? '';
|
||||
$start_date = $_POST['start_date'] ?? date('Y-m-d');
|
||||
$owner_id = !empty($_POST['owner_id']) ? (int)$_POST['owner_id'] : null;
|
||||
$est_completion = !empty($_POST['estimated_completion_date']) ? $_POST['estimated_completion_date'] : null;
|
||||
$type = $_POST['type'] ?? 'Internal';
|
||||
$est_hours = (float)($_POST['estimated_hours'] ?? 0);
|
||||
|
||||
if ($name && $code) {
|
||||
$stmt = db()->prepare("INSERT INTO projects (tenant_id, name, code, start_date) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$tenant_id, $name, $code, $start_date]);
|
||||
$stmt = db()->prepare("INSERT INTO projects (tenant_id, name, code, start_date, owner_id, estimated_completion_date, type, estimated_hours) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$tenant_id, $name, $code, $start_date, $owner_id, $est_completion, $type, $est_hours]);
|
||||
|
||||
// Log Activity
|
||||
$stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)");
|
||||
@ -206,51 +210,57 @@ $date_preset = $_GET['date_preset'] ?? '';
|
||||
$start_from = $_GET['start_from'] ?? '';
|
||||
$start_to = $_GET['start_to'] ?? '';
|
||||
|
||||
$query = "SELECT * FROM projects WHERE tenant_id = ?";
|
||||
$query = "
|
||||
SELECT p.*,
|
||||
CONCAT(e.first_name, ' ', e.last_name) as owner_name,
|
||||
COALESCE((SELECT SUM(hours) FROM labour_entries WHERE project_id = p.id), 0) as total_hours
|
||||
FROM projects p
|
||||
LEFT JOIN employees e ON p.owner_id = e.id
|
||||
WHERE p.tenant_id = ?";
|
||||
$params = [$tenant_id];
|
||||
|
||||
if ($search) {
|
||||
$query .= " AND (name LIKE ? OR code LIKE ?)";
|
||||
$query .= " AND (p.name LIKE ? OR p.code LIKE ?)";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
}
|
||||
|
||||
if ($status_filter) {
|
||||
$query .= " AND status = ?";
|
||||
$query .= " AND p.status = ?";
|
||||
$params[] = $status_filter;
|
||||
}
|
||||
|
||||
if ($date_preset && $date_preset !== 'custom') {
|
||||
switch ($date_preset) {
|
||||
case 'today':
|
||||
$query .= " AND start_date = CURRENT_DATE";
|
||||
$query .= " AND p.start_date = CURRENT_DATE";
|
||||
break;
|
||||
case 'this_week':
|
||||
$query .= " AND start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)";
|
||||
$query .= " AND p.start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)";
|
||||
break;
|
||||
case 'last_week':
|
||||
$query .= " AND start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) + 7 DAY) AND start_date < DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)";
|
||||
$query .= " AND p.start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) + 7 DAY) AND p.start_date < DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)";
|
||||
break;
|
||||
case 'this_month':
|
||||
$query .= " AND start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')";
|
||||
$query .= " AND p.start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')";
|
||||
break;
|
||||
case 'last_month':
|
||||
$query .= " AND start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m-01') AND start_date < DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')";
|
||||
$query .= " AND p.start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m-01') AND p.start_date < DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')";
|
||||
break;
|
||||
case 'this_year':
|
||||
$query .= " AND start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-01-01')";
|
||||
$query .= " AND p.start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-01-01')";
|
||||
break;
|
||||
case 'last_year':
|
||||
$query .= " AND start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 YEAR), '%Y-01-01') AND start_date < DATE_FORMAT(CURRENT_DATE, '%Y-01-01')";
|
||||
$query .= " AND p.start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 YEAR), '%Y-01-01') AND p.start_date < DATE_FORMAT(CURRENT_DATE, '%Y-01-01')";
|
||||
break;
|
||||
}
|
||||
} elseif ($date_preset === 'custom' && $start_from && $start_to) {
|
||||
$query .= " AND start_date BETWEEN ? AND ?";
|
||||
$query .= " AND p.start_date BETWEEN ? AND ?";
|
||||
$params[] = $start_from;
|
||||
$params[] = $start_to;
|
||||
}
|
||||
|
||||
$query .= " ORDER BY created_at DESC";
|
||||
$query .= " ORDER BY p.created_at DESC";
|
||||
$projects = db()->prepare($query);
|
||||
$projects->execute($params);
|
||||
$projectList = $projects->fetchAll();
|
||||
@ -310,6 +320,29 @@ $expenseEntries = db()->prepare("
|
||||
$expenseEntries->execute([$tenant_id]);
|
||||
$expenseList = $expenseEntries->fetchAll();
|
||||
|
||||
// Fetch Chart Data
|
||||
$chart_days = isset($_GET['chart_days']) ? (int)$_GET['chart_days'] : 7;
|
||||
if (!in_array($chart_days, [7, 14, 30])) $chart_days = 7;
|
||||
|
||||
$chartDataQuery = db()->prepare("
|
||||
SELECT entry_date, SUM(hours) as total_hours
|
||||
FROM labour_entries
|
||||
WHERE tenant_id = ? AND entry_date > DATE_SUB(CURRENT_DATE, INTERVAL ? DAY)
|
||||
GROUP BY entry_date
|
||||
ORDER BY entry_date ASC
|
||||
");
|
||||
$chartDataQuery->execute([$tenant_id, $chart_days]);
|
||||
$rawChartData = $chartDataQuery->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
|
||||
// Fill missing dates
|
||||
$chartLabels = [];
|
||||
$chartValues = [];
|
||||
for ($i = $chart_days - 1; $i >= 0; $i--) {
|
||||
$date = date('Y-m-d', strtotime("-$i days"));
|
||||
$chartLabels[] = date('M d', strtotime($date));
|
||||
$chartValues[] = (float)($rawChartData[$date] ?? 0);
|
||||
}
|
||||
|
||||
$activities = db()->prepare("SELECT * FROM activity_log WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 10");
|
||||
$activities->execute([$tenant_id]);
|
||||
$activityList = $activities->fetchAll();
|
||||
@ -326,6 +359,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="assets/css/custom.css?v=<?= time() ?>" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -373,7 +407,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">Reports</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#">Labour Summary</a></li>
|
||||
<li><a class="dropdown-item" href="reports.php?report_type=labour_export">Labour Export</a></li>
|
||||
<li><a class="dropdown-item" href="reports.php?report_type=calendar">Monthly Calendar</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#">Project Expenses</a></li>
|
||||
<li><a class="dropdown-item" href="#">SR&ED Claim Export</a></li>
|
||||
</ul>
|
||||
@ -388,8 +424,29 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Dashboard Chart Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3">
|
||||
<h5 class="mb-0 fw-bold">Labour Hours Overview</h5>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="?chart_days=7" class="btn btn-outline-primary <?= $chart_days == 7 ? 'active' : '' ?>">7 Days</a>
|
||||
<a href="?chart_days=14" class="btn btn-outline-primary <?= $chart_days == 14 ? 'active' : '' ?>">14 Days</a>
|
||||
<a href="?chart_days=30" class="btn btn-outline-primary <?= $chart_days == 30 ? 'active' : '' ?>">30 Days</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div style="height: 300px;">
|
||||
<canvas id="hoursChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<div class="col-lg-12">
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show border-0 shadow-sm mb-4" role="alert">
|
||||
<?php
|
||||
@ -422,21 +479,40 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Project Name</th>
|
||||
<th>Code</th>
|
||||
<th>Start Date</th>
|
||||
<th>Owner</th>
|
||||
<th>Type</th>
|
||||
<th>Hours (Logged/Est)</th>
|
||||
<th>Variance</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($projectList)): ?>
|
||||
<tr><td colspan="5" class="text-center py-4 text-muted">No projects found matching the filters.</td></tr>
|
||||
<tr><td colspan="7" class="text-center py-4 text-muted">No projects found matching the filters.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($projectList as $p): ?>
|
||||
<?php foreach ($projectList as $p):
|
||||
$total_hours = (float)$p['total_hours'];
|
||||
$est_hours = (float)$p['estimated_hours'];
|
||||
$variance = $est_hours - $total_hours;
|
||||
$is_over = $total_hours > $est_hours && $est_hours > 0;
|
||||
?>
|
||||
<tr>
|
||||
<td><strong><?= htmlspecialchars($p['name']) ?></strong></td>
|
||||
<td><code class="text-primary"><?= htmlspecialchars($p['code']) ?></code></td>
|
||||
<td><?= $p['start_date'] ?></td>
|
||||
<td>
|
||||
<strong><?= htmlspecialchars($p['name']) ?></strong><br>
|
||||
<code class="extra-small text-primary"><?= htmlspecialchars($p['code']) ?></code>
|
||||
</td>
|
||||
<td><small><?= htmlspecialchars($p['owner_name'] ?: 'Unassigned') ?></small></td>
|
||||
<td><span class="badge bg-light text-dark border"><?= $p['type'] ?></span></td>
|
||||
<td>
|
||||
<span class="fw-bold <?= $is_over ? 'text-danger' : '' ?>"><?= number_format($total_hours, 1) ?></span>
|
||||
<span class="text-muted">/ <?= number_format($est_hours, 1) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fw-bold <?= $is_over ? 'text-danger' : 'text-success' ?>">
|
||||
<?= ($variance >= 0 ? '+' : '') . number_format($variance, 1) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><span class="status-badge status-<?= str_replace('_', '-', $p['status']) ?>"><?= ucfirst(str_replace('_', ' ', $p['status'])) ?></span></td>
|
||||
<td class="text-end">
|
||||
<button class="btn btn-sm btn-outline-secondary">Edit</button>
|
||||
@ -627,29 +703,46 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-header">Activity Hub</div>
|
||||
<div class="card-body p-3">
|
||||
<ul class="activity-feed">
|
||||
<?php foreach ($activityList as $a): ?>
|
||||
<li class="activity-item">
|
||||
<div class="fw-bold small"><?= htmlspecialchars($a['action']) ?></div>
|
||||
<div class="text-muted extra-small"><?= htmlspecialchars($a['details']) ?></div>
|
||||
<div class="activity-time mt-1"><?= date('M d, H:i', strtotime($a['created_at'])) ?></div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<!-- Recent Activity Section -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0 fw-bold">Recent Activity</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 200px;">Time</th>
|
||||
<th>Action</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($activityList as $a): ?>
|
||||
<tr>
|
||||
<td class="text-muted small"><?= date('M d, Y H:i', strtotime($a['created_at'])) ?></td>
|
||||
<td><span class="badge bg-light text-primary border"><?= htmlspecialchars($a['action']) ?></span></td>
|
||||
<td class="small"><?= htmlspecialchars($a['details']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modals -->
|
||||
<div class="modal fade" id="addProjectModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold">Add New Project</h5>
|
||||
@ -657,17 +750,46 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
|
||||
</div>
|
||||
<form method="POST">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">Project Name</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">Project Code</label>
|
||||
<input type="text" name="code" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">Start Date</label>
|
||||
<input type="date" name="start_date" class="form-control" value="<?= date('Y-m-d') ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-8 mb-3">
|
||||
<label class="form-label small fw-bold">Project Name</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="e.g. 5G Network Optimization" required>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label small fw-bold">Project Code</label>
|
||||
<input type="text" name="code" class="form-control" placeholder="PRJ-001" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold">Project Owner</label>
|
||||
<select name="owner_id" class="form-select">
|
||||
<option value="">Select Owner...</option>
|
||||
<?php foreach ($employeeList as $e): ?>
|
||||
<option value="<?= $e['id'] ?>"><?= htmlspecialchars($e['first_name'] . ' ' . $e['last_name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold">Project Type</label>
|
||||
<select name="type" class="form-select">
|
||||
<option value="Internal">Internal</option>
|
||||
<option value="SRED">SR&ED</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold">Start Date</label>
|
||||
<input type="date" name="start_date" class="form-control" value="<?= date('Y-m-d') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold">Est. Completion Date</label>
|
||||
<input type="date" name="estimated_completion_date" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label small fw-bold">Estimated Hours</label>
|
||||
<div class="input-group">
|
||||
<input type="number" name="estimated_hours" class="form-control" step="0.5" min="0" value="0">
|
||||
<span class="input-group-text">hours</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
@ -979,6 +1101,44 @@ function handleDatePreset(value) {
|
||||
if (window.innerWidth < 992) {
|
||||
document.getElementById('projectFilterSidebar').classList.add('collapsed');
|
||||
}
|
||||
|
||||
// Hours Chart
|
||||
const ctx = document.getElementById('hoursChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: <?= json_encode($chartLabels) ?>,
|
||||
datasets: [{
|
||||
label: 'Logged Hours',
|
||||
data: <?= json_encode($chartValues) ?>,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
borderWidth: 3,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 4,
|
||||
pointBackgroundColor: '#3b82f6'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: '#f1f5f9' },
|
||||
ticks: { font: { size: 11 } }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { size: 11 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
363
reports.php
Normal file
363
reports.php
Normal file
@ -0,0 +1,363 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// Simulate Tenant Context
|
||||
$tenant_id = 1;
|
||||
|
||||
$report_type = $_GET['report_type'] ?? 'labour_export';
|
||||
|
||||
// Filter Data for Export Report
|
||||
$f_employee = $_GET['employee_id'] ?? '';
|
||||
$f_team = $_GET['team_id'] ?? '';
|
||||
$f_start = $_GET['start_date'] ?? date('Y-m-01');
|
||||
$f_end = $_GET['end_date'] ?? date('Y-m-t');
|
||||
$f_projects = $_GET['projects'] ?? []; // Array
|
||||
$f_activity = $_GET['activity_type_id'] ?? '';
|
||||
|
||||
// Handle Results for Labour Export
|
||||
$exportResults = [];
|
||||
$chartData = [];
|
||||
|
||||
if ($report_type === 'labour_export') {
|
||||
$query = "
|
||||
SELECT le.*, p.name as project_name, e.name as employee_name, lt.name as labour_type
|
||||
FROM labour_entries le
|
||||
JOIN projects p ON le.project_id = p.id
|
||||
JOIN employees e ON le.employee_id = e.id
|
||||
LEFT JOIN labour_types lt ON le.labour_type_id = lt.id
|
||||
WHERE le.tenant_id = ? AND le.entry_date BETWEEN ? AND ?
|
||||
";
|
||||
$params = [$tenant_id, $f_start, $f_end];
|
||||
|
||||
if ($f_employee) {
|
||||
$query .= " AND le.employee_id = ?";
|
||||
$params[] = $f_employee;
|
||||
}
|
||||
|
||||
if ($f_team) {
|
||||
$query .= " AND le.employee_id IN (SELECT employee_id FROM employee_teams WHERE team_id = ?)";
|
||||
$params[] = $f_team;
|
||||
}
|
||||
|
||||
if (!empty($f_projects)) {
|
||||
$placeholders = implode(',', array_fill(0, count($f_projects), '?'));
|
||||
$query .= " AND le.project_id IN ($placeholders)";
|
||||
foreach ($f_projects as $pid) $params[] = $pid;
|
||||
}
|
||||
|
||||
if ($f_activity) {
|
||||
$query .= " AND le.labour_type_id = ?";
|
||||
$params[] = $f_activity;
|
||||
}
|
||||
|
||||
$query .= " ORDER BY le.entry_date ASC";
|
||||
$stmt = db()->prepare($query);
|
||||
$stmt->execute($params);
|
||||
$exportResults = $stmt->fetchAll();
|
||||
|
||||
// Group for Chart (Hours per Day)
|
||||
$grouped = [];
|
||||
foreach ($exportResults as $row) {
|
||||
$grouped[$row['entry_date']] = ($grouped[$row['entry_date']] ?? 0) + (float)$row['hours'];
|
||||
}
|
||||
ksort($grouped);
|
||||
$chartData = [
|
||||
'labels' => array_keys($grouped),
|
||||
'values' => array_values($grouped)
|
||||
];
|
||||
}
|
||||
|
||||
// Handle Calendar Report
|
||||
$cal_month = $_GET['month'] ?? date('Y-m');
|
||||
$calendarData = [];
|
||||
if ($report_type === 'calendar') {
|
||||
$stmt = db()->prepare("
|
||||
SELECT entry_date, SUM(hours) as total_hours
|
||||
FROM labour_entries
|
||||
WHERE tenant_id = ? AND DATE_FORMAT(entry_date, '%Y-%m') = ?
|
||||
GROUP BY entry_date
|
||||
");
|
||||
$stmt->execute([$tenant_id, $cal_month]);
|
||||
$calendarData = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
}
|
||||
|
||||
// Fetch helper data
|
||||
$employees = db()->prepare("SELECT id, name FROM employees WHERE tenant_id = ? ORDER BY name");
|
||||
$employees->execute([$tenant_id]);
|
||||
$employeeList = $employees->fetchAll();
|
||||
|
||||
$teams = db()->prepare("SELECT id, name FROM teams WHERE tenant_id = ? ORDER BY name");
|
||||
$teams->execute([$tenant_id]);
|
||||
$teamList = $teams->fetchAll();
|
||||
|
||||
$projects = db()->prepare("SELECT id, name FROM projects WHERE tenant_id = ? ORDER BY name");
|
||||
$projects->execute([$tenant_id]);
|
||||
$projectList = $projects->fetchAll();
|
||||
|
||||
$labourTypes = db()->prepare("SELECT id, name FROM labour_types WHERE tenant_id = ? ORDER BY name");
|
||||
$labourTypes->execute([$tenant_id]);
|
||||
$labourTypeList = $labourTypes->fetchAll();
|
||||
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>SR&ED Manager - Reports</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="assets/css/custom.css?v=<?= time() ?>" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg sticky-top">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="index.php">SR&ED MANAGER</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="index.php">Dashboard</a></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">Projects</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="index.php">List Projects</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown">Reports</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="reports.php?report_type=labour_export">Labour Export</a></li>
|
||||
<li><a class="dropdown-item" href="reports.php?report_type=calendar">Monthly Calendar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<ul class="nav nav-pills mb-3">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $report_type === 'labour_export' ? 'active' : '' ?>" href="reports.php?report_type=labour_export">Labour Export</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= $report_type === 'calendar' ? 'active' : '' ?>" href="reports.php?report_type=calendar">Monthly Calendar</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<?php if ($report_type === 'labour_export'): ?>
|
||||
<form method="GET" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="report_type" value="labour_export">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold">Employee</label>
|
||||
<select name="employee_id" class="form-select form-select-sm">
|
||||
<option value="">All Employees</option>
|
||||
<?php foreach ($employeeList as $e): ?>
|
||||
<option value="<?= $e['id'] ?>" <?= $f_employee == $e['id'] ? 'selected' : '' ?>><?= htmlspecialchars($e['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold">Team</label>
|
||||
<select name="team_id" class="form-select form-select-sm">
|
||||
<option value="">All Teams</option>
|
||||
<?php foreach ($teamList as $t): ?>
|
||||
<option value="<?= $t['id'] ?>" <?= $f_team == $t['id'] ? 'selected' : '' ?>><?= htmlspecialchars($t['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold">Start Date</label>
|
||||
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= $f_start ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold">End Date</label>
|
||||
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= $f_end ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold">Projects (Multi-select)</label>
|
||||
<select name="projects[]" class="form-select form-select-sm" multiple style="height: 100px;">
|
||||
<?php foreach ($projectList as $p): ?>
|
||||
<option value="<?= $p['id'] ?>" <?= in_array($p['id'], $f_projects) ? 'selected' : '' ?>><?= htmlspecialchars($p['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="extra-small text-muted">Hold Ctrl/Cmd to select multiple</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold">Activity Type</label>
|
||||
<select name="activity_type_id" class="form-select form-select-sm">
|
||||
<option value="">All Activities</option>
|
||||
<?php foreach ($labourTypeList as $lt): ?>
|
||||
<option value="<?= $lt['id'] ?>" <?= $f_activity == $lt['id'] ? 'selected' : '' ?>><?= htmlspecialchars($lt['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-sm btn-primary w-100">Generate Report</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="GET" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="report_type" value="calendar">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small fw-bold">Select Month</label>
|
||||
<input type="month" name="month" class="form-control form-control-sm" value="<?= $cal_month ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-sm btn-primary w-100">View Calendar</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($report_type === 'labour_export'): ?>
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0 fw-bold">Detailed Labour Records</h6>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="window.print()"><i class="bi bi-printer"></i> Print / PDF</button>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Employee</th>
|
||||
<th>Project</th>
|
||||
<th>Activity</th>
|
||||
<th class="text-end">Hours</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($exportResults)): ?>
|
||||
<tr><td colspan="5" class="text-center py-5 text-muted">No records found for the selected criteria.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php $total = 0; foreach ($exportResults as $row): $total += (float)$row['hours']; ?>
|
||||
<tr>
|
||||
<td><?= $row['entry_date'] ?></td>
|
||||
<td><strong><?= htmlspecialchars($row['employee_name']) ?></strong></td>
|
||||
<td><?= htmlspecialchars($row['project_name']) ?></td>
|
||||
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($row['labour_type'] ?? 'Uncategorized') ?></span></td>
|
||||
<td class="text-end fw-bold"><?= number_format((float)$row['hours'], 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<?php if (!empty($exportResults)): ?>
|
||||
<tfoot class="bg-light">
|
||||
<tr>
|
||||
<td colspan="4" class="text-end fw-bold">Total Hours:</td>
|
||||
<td class="text-end fw-bold text-primary"><?= number_format($total, 2) ?></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0 fw-bold">Hours Breakdown</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="exportChart" style="height: 300px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ctx = document.getElementById('exportChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?= json_encode($chartData['labels']) ?>,
|
||||
datasets: [{
|
||||
label: 'Hours',
|
||||
data: <?= json_encode($chartData['values']) ?>,
|
||||
backgroundColor: 'rgba(13, 110, 253, 0.2)',
|
||||
borderColor: '#0d6efd',
|
||||
borderWidth: 2,
|
||||
borderRadius: 5
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
y: { beginAtZero: true, grid: { display: false } },
|
||||
x: { grid: { display: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php elseif ($report_type === 'calendar'): ?>
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0 fw-bold">Monthly Labour Calendar - <?= date('F Y', strtotime($cal_month)) ?></h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="calendar-grid">
|
||||
<div class="calendar-header-day">Sun</div>
|
||||
<div class="calendar-header-day">Mon</div>
|
||||
<div class="calendar-header-day">Tue</div>
|
||||
<div class="calendar-header-day">Wed</div>
|
||||
<div class="calendar-header-day">Thu</div>
|
||||
<div class="calendar-header-day">Fri</div>
|
||||
<div class="calendar-header-day">Sat</div>
|
||||
|
||||
<?php
|
||||
$first_day = date('Y-m-01', strtotime($cal_month));
|
||||
$days_in_month = date('t', strtotime($cal_month));
|
||||
$start_day_of_week = (int)date('w', strtotime($first_day));
|
||||
|
||||
// Fill empty days before first of month
|
||||
for ($i = 0; $i < $start_day_of_week; $i++) {
|
||||
echo '<div class="calendar-day other-month"></div>';
|
||||
}
|
||||
|
||||
// Days of month
|
||||
for ($day = 1; $day <= $days_in_month; $day++) {
|
||||
$current_date = date('Y-m-', strtotime($cal_month)) . str_pad((string)$day, 2, '0', STR_PAD_LEFT);
|
||||
$hours = $calendarData[$current_date] ?? 0;
|
||||
?>
|
||||
<div class="calendar-day">
|
||||
<span class="day-number"><?= $day ?></span>
|
||||
<?php if ($hours > 0): ?>
|
||||
<span class="day-hours"><?= number_format((float)$hours, 1) ?><small class="ms-1 h6 mb-0" style="font-size: 0.6rem;">h</small></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Fill remaining days to complete grid
|
||||
$total_cells = ceil(($start_day_of_week + $days_in_month) / 7) * 7;
|
||||
for ($i = ($start_day_of_week + $days_in_month); $i < $total_cells; $i++) {
|
||||
echo '<div class="calendar-day other-month"></div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user