From 444cd73cc6857d8eed8627d2a29e551aa8866579 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 15 Feb 2026 01:02:26 +0000 Subject: [PATCH] reports --- assets/css/custom.css | 57 ++++ db/migrations/005_enhanced_projects.sql | 7 + index.php | 256 +++++++++++++---- reports.php | 363 ++++++++++++++++++++++++ 4 files changed, 635 insertions(+), 48 deletions(-) create mode 100644 db/migrations/005_enhanced_projects.sql create mode 100644 reports.php diff --git a/assets/css/custom.css b/assets/css/custom.css index b121a81..88952e8 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -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; +} + diff --git a/db/migrations/005_enhanced_projects.sql b/db/migrations/005_enhanced_projects.sql new file mode 100644 index 0000000..e350ce9 --- /dev/null +++ b/db/migrations/005_enhanced_projects.sql @@ -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; diff --git a/index.php b/index.php index bc70028..52551ec 100644 --- a/index.php +++ b/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 + @@ -373,7 +407,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking