prepare(" SELECT * FROM projects WHERE id = ? AND client_id = ? "); $stmt->execute([$project_id, $_SESSION['client_id']]); $project = $stmt->fetch(); if (!$project) { header('Location: projects-list.php'); exit; } // Fetch selections for this project try { $stmt = $pdo->prepare(" SELECT ps.*, COUNT(DISTINCT sm.user_id) as actual_selected, c.company_name as created_by_name, SUM(CASE WHEN sm.sample_status = 'pending' THEN 1 ELSE 0 END) as cnt_pending, SUM(CASE WHEN sm.sample_status = 'complete' THEN 1 ELSE 0 END) as cnt_complete, SUM(CASE WHEN sm.sample_status = 'screen_out' THEN 1 ELSE 0 END) as cnt_screen_out, SUM(CASE WHEN sm.sample_status = 'quota_full' THEN 1 ELSE 0 END) as cnt_quota_full, SUM(CASE WHEN sm.sample_status = 'over_quota' THEN 1 ELSE 0 END) as cnt_over_quota FROM project_selections ps LEFT JOIN selection_members sm ON ps.id = sm.selection_id LEFT JOIN clients c ON ps.created_by = c.id WHERE ps.project_id = ? AND ps.client_id = ? GROUP BY ps.id ORDER BY ps.created_at DESC "); $stmt->execute([$project_id, $_SESSION['client_id']]); $selections = $stmt->fetchAll(); // Get count of URLs sent per selection (via sent_to_user_id matching selection_members) $selectionUrlCounts = []; $selectionLoiStats = []; $projectLoiStats = ['avg_loi' => 0, 'min_loi' => 0, 'max_loi' => 0, 'total_completes' => 0, 'speedsters' => 0, 'ip_dups' => 0]; if (!empty($selections)) { $selIds = array_column($selections, 'id'); $placeholders = implode(',', array_fill(0, count($selIds), '?')); $stmt = $pdo->prepare(" SELECT sm.selection_id, COUNT(DISTINCT su.id) as urls_sent, SUM(CASE WHEN su.status = 'complete' THEN 1 ELSE 0 END) as urls_complete, SUM(CASE WHEN su.status = 'partial' THEN 1 ELSE 0 END) as urls_partial, SUM(CASE WHEN su.status IN ('earlyscreenout','latescreenout') THEN 1 ELSE 0 END) as urls_screenout, SUM(CASE WHEN su.status = 'timeout' THEN 1 ELSE 0 END) as urls_timeout, SUM(CASE WHEN su.status = 'quotafull' THEN 1 ELSE 0 END) as urls_quotafull FROM selection_members sm INNER JOIN survey_urls su ON su.sent_to_user_id = sm.user_id AND su.project_id = ? WHERE sm.selection_id IN ($placeholders) GROUP BY sm.selection_id "); $stmt->execute(array_merge([$project['project_id']], $selIds)); foreach ($stmt->fetchAll() as $row) { $selectionUrlCounts[$row['selection_id']] = $row; } // LOI stats per selection $stmt = $pdo->prepare(" SELECT sm.selection_id, AVG(CASE WHEN su.status = 'complete' AND su.actual_loi_seconds IS NOT NULL THEN su.actual_loi_seconds END) as avg_loi, MIN(CASE WHEN su.status = 'complete' AND su.actual_loi_seconds IS NOT NULL THEN su.actual_loi_seconds END) as min_loi, MAX(CASE WHEN su.status = 'complete' AND su.actual_loi_seconds IS NOT NULL THEN su.actual_loi_seconds END) as max_loi, SUM(CASE WHEN su.quality_flag = 'speedster' THEN 1 ELSE 0 END) as speedsters, SUM(CASE WHEN su.quality_flag = 'ip_duplicate' THEN 1 ELSE 0 END) as ip_dups FROM selection_members sm INNER JOIN survey_urls su ON su.sent_to_user_id = sm.user_id AND su.project_id = ? WHERE sm.selection_id IN ($placeholders) GROUP BY sm.selection_id "); $stmt->execute(array_merge([$project['project_id']], $selIds)); foreach ($stmt->fetchAll() as $row) { $selectionLoiStats[$row['selection_id']] = $row; } // Project-wide LOI stats $stmt = $pdo->prepare(" SELECT AVG(CASE WHEN status = 'complete' AND actual_loi_seconds IS NOT NULL THEN actual_loi_seconds END) as avg_loi, MIN(CASE WHEN status = 'complete' AND actual_loi_seconds IS NOT NULL THEN actual_loi_seconds END) as min_loi, MAX(CASE WHEN status = 'complete' AND actual_loi_seconds IS NOT NULL THEN actual_loi_seconds END) as max_loi, SUM(CASE WHEN status = 'complete' THEN 1 ELSE 0 END) as total_completes, SUM(CASE WHEN quality_flag = 'speedster' THEN 1 ELSE 0 END) as speedsters, SUM(CASE WHEN quality_flag = 'ip_duplicate' THEN 1 ELSE 0 END) as ip_dups FROM survey_urls WHERE project_id = ? "); $stmt->execute([$project['project_id']]); $pStats = $stmt->fetch(PDO::FETCH_ASSOC); if ($pStats) $projectLoiStats = $pStats; } } catch (Exception $e) { error_log("Selections fetch error: " . $e->getMessage()); $selections = []; $selectionUrlCounts = []; } $page_title = 'View Project'; $is_live = $project['status'] === 'Live'; // Status colors $status_colors = [ 'Created' => '#0066cc', 'Targeted' => '#ffc107', 'Live' => '#28a745', 'On hold' => '#dc2626', 'Closed' => '#666' ]; $status_color = $status_colors[$project['status']] ?? '#666'; // Define redirect statuses $redirect_statuses = [ 'complete' => ['name' => 'Complete', 'color' => '#28a745'], 'partial' => ['name' => 'Partial Complete', 'color' => '#0066cc'], 'earlyscreenout' => ['name' => 'Early Screen Out', 'color' => '#dc2626'], 'latescreenout' => ['name' => 'Late Screen Out', 'color' => '#ffc107'], 'timeout' => ['name' => 'Timed Out', 'color' => '#666'], 'quotafull' => ['name' => 'Quota Full', 'color' => '#ff6b6b'] ]; $base_url = 'https://relevantreflex.com/r/'; include 'client-portal-header.php'; ?>
| Selection ID | Name | Status | Required | Selected | Sample Status | URLs Sent | Avg LOI | Quality Flags | Incidence | Created | Actions |
|---|---|---|---|---|---|---|---|---|---|---|---|
|
0): ?>
Pending:
0): ?>
Complete:
0): ?>
Screen Out:
0): ?>
Quota Full:
0): ?>
Over Quota:
No members
|
0): ?>
Sent:
0): ?>
0): ?>
SO:
0): ?>
TO:
None
|
m - |
0 || $ipDups > 0): ?>
0): ?>
⚡ Speedster 1 ? 's' : ''; ?>
0): ?>
🔗 IP Dup 1 ? 's' : ''; ?>
Clean
|
% |
| Batch # | Type | Total | Status Breakdown | Created | Actions |
|---|---|---|---|---|---|
| Batch # |
['label' => 'Available', 'class' => 'pill-available'],
'sent' => ['label' => 'Sent', 'class' => 'pill-sent'],
'clicked' => ['label' => 'Clicked', 'class' => 'pill-clicked'],
'complete' => ['label' => 'Complete', 'class' => 'pill-complete'],
'partial' => ['label' => 'Partial', 'class' => 'pill-partial'],
'earlyscreenout' => ['label' => 'Early SO', 'class' => 'pill-earlyscreenout'],
'latescreenout' => ['label' => 'Late SO', 'class' => 'pill-latescreenout'],
'timeout' => ['label' => 'Timeout', 'class' => 'pill-timeout'],
'quotafull' => ['label' => 'Quota Full', 'class' => 'pill-quotafull'],
];
?>
$status_info): ?>
0): ?>
:
No activity yet
|
🔍 Note: URLs that remain without a result for over 2 hours are automatically marked as Timeout.