Initial commit - TPU Survey System with Google Forms & Certificate Generator
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
/**
|
||||
* Statistics API
|
||||
* GET ?action=summary&survey_id=X — Summary statistics
|
||||
* GET ?action=detail&survey_id=X — Detailed per-question stats
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
require_once __DIR__ . '/../config/auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
requireLogin();
|
||||
|
||||
$action = $_GET['action'] ?? 'summary';
|
||||
$surveyId = (int)($_GET['survey_id'] ?? 0);
|
||||
|
||||
if (!$surveyId) jsonResponse(['error' => 'Missing survey_id'], 400);
|
||||
|
||||
// Check access
|
||||
$db = getDB();
|
||||
if (!isAdmin()) {
|
||||
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$survey = $stmt->fetch();
|
||||
if (!$survey || $survey['user_id'] != $_SESSION['user_id']) {
|
||||
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'summary':
|
||||
getSummary($surveyId);
|
||||
break;
|
||||
case 'detail':
|
||||
getDetail($surveyId);
|
||||
break;
|
||||
default:
|
||||
jsonResponse(['error' => 'Invalid action'], 400);
|
||||
}
|
||||
|
||||
function getSummary($surveyId) {
|
||||
$db = getDB();
|
||||
|
||||
// Total responses
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as total FROM survey_responses WHERE survey_id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$total = $stmt->fetch()['total'];
|
||||
|
||||
// Get survey info
|
||||
$stmt = $db->prepare("SELECT title, status, created_at FROM surveys WHERE id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$surveyInfo = $stmt->fetch();
|
||||
|
||||
// Get overall mean and SD for radio/rating questions
|
||||
$stmt = $db->prepare("
|
||||
SELECT
|
||||
AVG(CAST(ra.answer_value AS DECIMAL(10,2))) as overall_mean,
|
||||
STDDEV(CAST(ra.answer_value AS DECIMAL(10,2))) as overall_sd
|
||||
FROM response_answers ra
|
||||
JOIN survey_questions sq ON ra.question_id = sq.id
|
||||
JOIN survey_sections ss ON sq.section_id = ss.id
|
||||
WHERE ss.survey_id = ? AND sq.question_type = 'radio' AND sq.options IS NULL AND ra.answer_value REGEXP '^[0-9]+$'
|
||||
");
|
||||
$stmt->execute([$surveyId]);
|
||||
$overall = $stmt->fetch();
|
||||
|
||||
jsonResponse([
|
||||
'survey' => $surveyInfo,
|
||||
'total_responses' => (int)$total,
|
||||
'overall_mean' => $overall['overall_mean'] ? round((float)$overall['overall_mean'], 2) : 0,
|
||||
'overall_sd' => $overall['overall_sd'] ? round((float)$overall['overall_sd'], 2) : 0
|
||||
]);
|
||||
}
|
||||
|
||||
function getDetail($surveyId) {
|
||||
$db = getDB();
|
||||
|
||||
// Total responses
|
||||
$stmt = $db->prepare("SELECT COUNT(*) as total FROM survey_responses WHERE survey_id = ?");
|
||||
$stmt->execute([$surveyId]);
|
||||
$totalResponses = (int)$stmt->fetch()['total'];
|
||||
|
||||
// Get sections with questions
|
||||
$stmt = $db->prepare("SELECT * FROM survey_sections WHERE survey_id = ? ORDER BY section_number");
|
||||
$stmt->execute([$surveyId]);
|
||||
$sections = $stmt->fetchAll();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$qStmt = $db->prepare("SELECT * FROM survey_questions WHERE section_id = ? ORDER BY order_num");
|
||||
$qStmt->execute([$section['id']]);
|
||||
$questions = $qStmt->fetchAll();
|
||||
|
||||
$sectionStats = [
|
||||
'section_number' => $section['section_number'],
|
||||
'title' => $section['title'],
|
||||
'headings' => [],
|
||||
'questions' => []
|
||||
];
|
||||
|
||||
// Load headings for this section
|
||||
$headingStmt = $db->prepare("SELECT * FROM survey_headings WHERE section_id = ? ORDER BY order_num");
|
||||
$headingStmt->execute([$section['id']]);
|
||||
$headings = $headingStmt->fetchAll();
|
||||
|
||||
foreach ($questions as $q) {
|
||||
$qStat = [
|
||||
'id' => $q['id'],
|
||||
'heading_id' => $q['heading_id'] ?? null,
|
||||
'question_text' => $q['question_text'],
|
||||
'question_type' => $q['question_type']
|
||||
];
|
||||
|
||||
if ($q['question_type'] === 'radio' && empty($q['options'])) {
|
||||
// Calculate mean, SD, frequency for rating questions (1-5)
|
||||
$aStmt = $db->prepare("
|
||||
SELECT answer_value, COUNT(*) as cnt
|
||||
FROM response_answers
|
||||
WHERE question_id = ? AND answer_value REGEXP '^[0-9]+$'
|
||||
GROUP BY answer_value
|
||||
ORDER BY answer_value
|
||||
");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$freqData = $aStmt->fetchAll();
|
||||
|
||||
$frequencies = [];
|
||||
$values = [];
|
||||
$totalAnswers = 0;
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$frequencies[$i] = ['value' => $i, 'count' => 0, 'percentage' => 0];
|
||||
}
|
||||
|
||||
foreach ($freqData as $fd) {
|
||||
$val = (int)$fd['answer_value'];
|
||||
if ($val >= 1 && $val <= 5) {
|
||||
$frequencies[$val]['count'] = (int)$fd['cnt'];
|
||||
$totalAnswers += (int)$fd['cnt'];
|
||||
for ($j = 0; $j < (int)$fd['cnt']; $j++) {
|
||||
$values[] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
foreach ($frequencies as &$f) {
|
||||
$f['percentage'] = $totalAnswers > 0 ? round(($f['count'] / $totalAnswers) * 100, 1) : 0;
|
||||
}
|
||||
unset($f);
|
||||
|
||||
// Calculate mean
|
||||
$mean = count($values) > 0 ? array_sum($values) / count($values) : 0;
|
||||
|
||||
// Calculate SD
|
||||
$sd = 0;
|
||||
if (count($values) > 1) {
|
||||
$sumSquaredDiff = 0;
|
||||
foreach ($values as $v) {
|
||||
$sumSquaredDiff += pow($v - $mean, 2);
|
||||
}
|
||||
$sd = sqrt($sumSquaredDiff / (count($values) - 1));
|
||||
}
|
||||
|
||||
// Interpretation
|
||||
$interpretation = '';
|
||||
if ($mean >= 4.51) $interpretation = 'มากที่สุด';
|
||||
elseif ($mean >= 3.51) $interpretation = 'มาก';
|
||||
elseif ($mean >= 2.51) $interpretation = 'ปานกลาง';
|
||||
elseif ($mean >= 1.51) $interpretation = 'น้อย';
|
||||
else $interpretation = 'น้อยที่สุด';
|
||||
|
||||
$qStat['mean'] = round($mean, 2);
|
||||
$qStat['sd'] = round($sd, 2);
|
||||
$qStat['interpretation'] = $interpretation;
|
||||
$qStat['frequencies'] = array_values($frequencies);
|
||||
$qStat['total_answers'] = $totalAnswers;
|
||||
|
||||
} elseif ($q['question_type'] === 'text') {
|
||||
// Collect text responses
|
||||
$aStmt = $db->prepare("
|
||||
SELECT answer_value FROM response_answers
|
||||
WHERE question_id = ? AND answer_value != ''
|
||||
ORDER BY id DESC
|
||||
");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$qStat['text_responses'] = array_column($aStmt->fetchAll(), 'answer_value');
|
||||
$qStat['total_answers'] = count($qStat['text_responses']);
|
||||
|
||||
} elseif (in_array($q['question_type'], ['dropdown', 'checkbox', 'radio'])) {
|
||||
// Frequency for dropdown/checkbox/custom radio
|
||||
$aStmt = $db->prepare("
|
||||
SELECT answer_value, COUNT(*) as cnt
|
||||
FROM response_answers
|
||||
WHERE question_id = ?
|
||||
GROUP BY answer_value
|
||||
ORDER BY cnt DESC
|
||||
");
|
||||
$aStmt->execute([$q['id']]);
|
||||
$freqData = $aStmt->fetchAll();
|
||||
|
||||
$totalAnswers = array_sum(array_column($freqData, 'cnt'));
|
||||
$qStat['frequencies'] = array_map(function($fd) use ($totalAnswers) {
|
||||
return [
|
||||
'value' => $fd['answer_value'],
|
||||
'count' => (int)$fd['cnt'],
|
||||
'percentage' => $totalAnswers > 0 ? round(((int)$fd['cnt'] / $totalAnswers) * 100, 1) : 0
|
||||
];
|
||||
}, $freqData);
|
||||
$qStat['total_answers'] = $totalAnswers;
|
||||
}
|
||||
|
||||
$sectionStats['questions'][] = $qStat;
|
||||
}
|
||||
|
||||
// Build heading stats groups
|
||||
$headingStats = [];
|
||||
foreach ($headings as $h) {
|
||||
$hStat = [
|
||||
'id' => $h['id'],
|
||||
'title' => $h['title'],
|
||||
'description' => $h['description'],
|
||||
'questions' => []
|
||||
];
|
||||
foreach ($sectionStats['questions'] as $qs) {
|
||||
if (($qs['heading_id'] ?? null) == $h['id']) {
|
||||
$hStat['questions'][] = $qs;
|
||||
}
|
||||
}
|
||||
$headingStats[] = $hStat;
|
||||
}
|
||||
$sectionStats['headings'] = $headingStats;
|
||||
|
||||
// Filter loose questions (no heading)
|
||||
$sectionStats['questions'] = array_values(array_filter($sectionStats['questions'], function($qs) {
|
||||
return empty($qs['heading_id']);
|
||||
}));
|
||||
|
||||
$result[] = $sectionStats;
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'total_responses' => $totalResponses,
|
||||
'sections' => $result
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user