prepare(" SELECT status FROM projects WHERE id = ? AND client_id = ? "); $checkStmt->execute([$project_id, $_SESSION['client_id']]); $project = $checkStmt->fetch(); if ($project && $project['status'] !== 'Live') { $deleteStmt = $pdo->prepare("DELETE FROM projects WHERE id = ?"); $deleteStmt->execute([$project_id]); $success = 'Project deleted successfully.'; } else { $error = 'Cannot delete this project. Live projects cannot be deleted.'; } } // Get filter values $filter_status = $_GET['status'] ?? ''; $filter_industry = $_GET['industry'] ?? ''; $filter_from_date = $_GET['from_date'] ?? ''; $filter_to_date = $_GET['to_date'] ?? ''; $filter_min_loi = $_GET['min_loi'] ?? ''; $filter_max_loi = $_GET['max_loi'] ?? ''; // Build query with filters $query = "SELECT * FROM projects WHERE client_id = ?"; $params = [$_SESSION['client_id']]; if ($filter_status) { $query .= " AND status = ?"; $params[] = $filter_status; } if ($filter_industry) { $query .= " AND industry = ?"; $params[] = $filter_industry; } if ($filter_from_date) { $query .= " AND DATE(created_at) >= ?"; $params[] = $filter_from_date; } if ($filter_to_date) { $query .= " AND DATE(created_at) <= ?"; $params[] = $filter_to_date; } if ($filter_min_loi) { $query .= " AND eloi >= ?"; $params[] = intval($filter_min_loi); } if ($filter_max_loi) { $query .= " AND eloi <= ?"; $params[] = intval($filter_max_loi); } $query .= " ORDER BY created_at DESC"; $stmt = $pdo->prepare($query); $stmt->execute($params); $projects = $stmt->fetchAll(); // ─── Per-project stats: aggregate survey_urls counts + avg actual LOI ─── $statsByProject = []; // project_id (varchar) => stats array $estIncByProject = []; // project_id (varchar) => weighted-avg estimated incidence if (!empty($projects)) { $project_id_list = array_map(function($p) { return $p['project_id']; }, $projects); $placeholders = implode(',', array_fill(0, count($project_id_list), '?')); // Aggregate survey_urls by project_id for url counts + avg actual LOI on completes $sStmt = $pdo->prepare(" SELECT project_id, COUNT(*) AS urls_total, SUM(CASE WHEN status = 'sent' THEN 1 ELSE 0 END) AS urls_sent, SUM(CASE WHEN status = 'clicked' THEN 1 ELSE 0 END) AS urls_clicked, SUM(CASE WHEN status = 'complete' THEN 1 ELSE 0 END) AS urls_complete, SUM(CASE WHEN status = 'partial' THEN 1 ELSE 0 END) AS urls_partial, SUM(CASE WHEN status IN ('earlyscreenout','latescreenout') THEN 1 ELSE 0 END) AS urls_screenout, SUM(CASE WHEN status = 'quotafull' THEN 1 ELSE 0 END) AS urls_quotafull, SUM(CASE WHEN status = 'timeout' THEN 1 ELSE 0 END) AS urls_timeout, ROUND(AVG(CASE WHEN status = 'complete' AND actual_loi_seconds > 0 THEN actual_loi_seconds END), 0) AS avg_loi_secs FROM survey_urls WHERE project_id IN ($placeholders) GROUP BY project_id "); $sStmt->execute($project_id_list); foreach ($sStmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $statsByProject[$row['project_id']] = $row; } // Weighted-average estimated incidence per project (using project_selections) // Note: project_selections.project_id is the int FK referencing projects.id $project_pk_list = array_map(function($p) { return (int)$p['id']; }, $projects); $pkPlaceholders = implode(',', array_fill(0, count($project_pk_list), '?')); $eStmt = $pdo->prepare(" SELECT project_id AS pk_id, CASE WHEN SUM(required_samples) > 0 THEN ROUND(SUM(estimated_incidence_rate * required_samples) / SUM(required_samples), 2) ELSE NULL END AS weighted_eir FROM project_selections WHERE project_id IN ($pkPlaceholders) GROUP BY project_id "); $eStmt->execute($project_pk_list); $eByPk = []; foreach ($eStmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $eByPk[(int)$row['pk_id']] = $row['weighted_eir']; } foreach ($projects as $p) { $estIncByProject[$p['project_id']] = $eByPk[(int)$p['id']] ?? null; } } // Helper: format LOI seconds → "Xm Ys" or "Xs" if (!function_exists('fmtLoi')) { function fmtLoi($secs) { if (!$secs || $secs <= 0) return '—'; $secs = (int)$secs; if ($secs < 60) return $secs . 's'; $m = floor($secs / 60); $s = $secs % 60; return $m . 'm' . ($s > 0 ? ' ' . $s . 's' : ''); } } // Helper: returns CSS color for a metric based on healthy/borderline/poor thresholds // $direction: 'higher_better' (response, incidence achievement) or 'lower_better' (screenout) if (!function_exists('metricColor')) { function metricColor($value, $good, $ok, $direction = 'higher_better') { if ($direction === 'higher_better') { if ($value >= $good) return '#059669'; // green if ($value >= $ok) return '#d97706'; // amber return '#dc2626'; // red } else { if ($value <= $good) return '#059669'; if ($value <= $ok) return '#d97706'; return '#dc2626'; } } } // Get industries for filter $industriesStmt = $pdo->query("SELECT DISTINCT industry FROM projects WHERE client_id = " . $_SESSION['client_id'] . " ORDER BY industry"); $industries = $industriesStmt->fetchAll(); include 'client-portal-header.php'; ?>

Projects

Manage your survey projects

+ Create New Project

Filters

Clear
Export Filtered Projects (PDF)

Projects ()

No projects found

Create your first project to get started!

+ Create Project
'#059669', 'Targeted' => '#ffc107', 'Live' => '#28a745', 'On hold' => '#dc2626', 'Closed' => '#666' ]; $status_color = $status_colors[$project['status']] ?? '#666'; $is_live = $project['status'] === 'Live'; // Pull stats for this project $st = $statsByProject[$project['project_id']] ?? null; $urls_sent = $st ? (int)$st['urls_sent'] : 0; $urls_clicked = $st ? (int)$st['urls_clicked'] : 0; $urls_complete = $st ? (int)$st['urls_complete'] : 0; $urls_partial = $st ? (int)$st['urls_partial'] : 0; $urls_screenout = $st ? (int)$st['urls_screenout'] : 0; $urls_quotafull = $st ? (int)$st['urls_quotafull'] : 0; $urls_timeout = $st ? (int)$st['urls_timeout'] : 0; $avg_loi_secs = $st && $st['avg_loi_secs'] ? (int)$st['avg_loi_secs'] : 0; $totalClicks = $urls_clicked + $urls_complete + $urls_partial + $urls_screenout + $urls_quotafull + $urls_timeout; $totalDispatched = $urls_sent + $totalClicks; $responseRate = $totalDispatched > 0 ? ($totalClicks / $totalDispatched) * 100 : 0; $incidenceRate = $totalClicks > 0 ? ($urls_complete / $totalClicks) * 100 : 0; $screenoutRate = $totalDispatched > 0 ? ($urls_screenout / $totalDispatched) * 100 : 0; $estIncidence = $estIncByProject[$project['project_id']] ?? null; $eloiSecs = (int)$project['eloi'] * 60; // Color codes $respColor = metricColor($responseRate, 20, 10, 'higher_better'); $soColor = metricColor($screenoutRate, 30, 50, 'lower_better'); $incColor = '#666'; if ($estIncidence > 0 && $totalClicks >= 10) { $achievementPct = ($incidenceRate / $estIncidence) * 100; $incColor = metricColor($achievementPct, 90, 70, 'higher_better'); } $loiColor = '#666'; if ($avg_loi_secs > 0 && $eloiSecs > 0) { $ratio = $avg_loi_secs / $eloiSecs; // healthy if 70%–130% of estimated, amber if 50-150, red otherwise if ($ratio >= 0.7 && $ratio <= 1.3) $loiColor = '#059669'; elseif ($ratio >= 0.5 && $ratio <= 1.5) $loiColor = '#d97706'; else $loiColor = '#dc2626'; } ?>
Project Status Sample
target / done
Sent Funnel
C / P / SO / QF / TO
Resp % Inc %
vs estimated
SO % Avg LOI
vs eLOI
Deadline Actions
· Ref:
0): ?>
done
% of target
0): ?>
awaiting
0): ?> / / / / 0): ?>
in progress
0): ?> % 0): ?> %
est %
est %
0): ?> % 0): ?>
est m
est m

Funnel: Complete / Partial / SO Screen-out / QF Quota-full / TO Timeout  ·  Resp% = clicks ÷ dispatched  ·  Inc% = completes ÷ clicks  ·  SO% = screen-outs ÷ dispatched