Fix statistics calculation for imported Likert scale responses and support text rating answers
This commit is contained in:
+43
-5
@@ -45,6 +45,41 @@ switch ($action) {
|
|||||||
jsonResponse(['error' => 'Invalid action'], 400);
|
jsonResponse(['error' => 'Invalid action'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseRatingScore($str) {
|
||||||
|
$trimmed = trim((string)$str);
|
||||||
|
if ($trimmed === '') return null;
|
||||||
|
|
||||||
|
if (is_numeric($trimmed)) {
|
||||||
|
$num = (int)$trimmed;
|
||||||
|
return ($num >= 1 && $num <= 5) ? $num : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^([1-5])\s*[\.\:\-\(\s]/u', $trimmed, $m)) {
|
||||||
|
return (int)$m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb_strpos($trimmed, 'มากที่สุด') !== false || mb_strpos($trimmed, 'ดีมาก') !== false || mb_strpos($trimmed, 'ยอดเยี่ยม') !== false || mb_strpos($trimmed, 'ดีเยี่ยม') !== false || mb_strpos($trimmed, 'เห็นด้วยอย่างยิ่ง') !== false) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'น้อยที่สุด') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วยอย่างยิ่ง') !== false) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'มาก') !== false || mb_strpos($trimmed, 'ดี') !== false || mb_strpos($trimmed, 'เห็นด้วย') !== false) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'ปานกลาง') !== false || mb_strpos($trimmed, 'พอใช้') !== false || mb_strpos($trimmed, 'ไม่แน่ใจ') !== false) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'น้อย') !== false || mb_strpos($trimmed, 'ควรปรับปรุง') !== false || mb_strpos($trimmed, 'ต้องปรับปรุง') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วย') !== false) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'ปรับปรุง') !== false) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function getStatsData($surveyId) {
|
function getStatsData($surveyId) {
|
||||||
$db = getDB();
|
$db = getDB();
|
||||||
|
|
||||||
@@ -78,7 +113,7 @@ function getStatsData($surveyId) {
|
|||||||
$aStmt = $db->prepare("
|
$aStmt = $db->prepare("
|
||||||
SELECT answer_value, COUNT(*) as cnt
|
SELECT answer_value, COUNT(*) as cnt
|
||||||
FROM response_answers
|
FROM response_answers
|
||||||
WHERE question_id = ? AND answer_value REGEXP '^[0-9]+$'
|
WHERE question_id = ? AND answer_value != ''
|
||||||
GROUP BY answer_value
|
GROUP BY answer_value
|
||||||
");
|
");
|
||||||
$aStmt->execute([$q['id']]);
|
$aStmt->execute([$q['id']]);
|
||||||
@@ -89,10 +124,13 @@ function getStatsData($surveyId) {
|
|||||||
for ($i = 1; $i <= 5; $i++) $freq[$i] = 0;
|
for ($i = 1; $i <= 5; $i++) $freq[$i] = 0;
|
||||||
|
|
||||||
foreach ($freqData as $fd) {
|
foreach ($freqData as $fd) {
|
||||||
$val = (int)$fd['answer_value'];
|
$rawVal = trim($fd['answer_value']);
|
||||||
|
$cnt = (int)$fd['cnt'];
|
||||||
|
$val = parseRatingScore($rawVal);
|
||||||
|
|
||||||
if ($val >= 1 && $val <= 5) {
|
if ($val >= 1 && $val <= 5) {
|
||||||
$freq[$val] = (int)$fd['cnt'];
|
$freq[$val] += $cnt;
|
||||||
for ($j = 0; $j < (int)$fd['cnt']; $j++) $values[] = $val;
|
for ($j = 0; $j < $cnt; $j++) $values[] = $val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +154,7 @@ function getStatsData($surveyId) {
|
|||||||
else $q['interpretation'] = 'น้อยที่สุด';
|
else $q['interpretation'] = 'น้อยที่สุด';
|
||||||
|
|
||||||
} elseif (in_array($q['question_type'], ['dropdown', 'checkbox', 'radio'])) {
|
} elseif (in_array($q['question_type'], ['dropdown', 'checkbox', '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 = $db->prepare("SELECT answer_value, COUNT(*) as cnt FROM response_answers WHERE question_id = ? AND answer_value != '' GROUP BY answer_value ORDER BY cnt DESC");
|
||||||
$aStmt->execute([$q['id']]);
|
$aStmt->execute([$q['id']]);
|
||||||
$q['frequencies'] = $aStmt->fetchAll();
|
$q['frequencies'] = $aStmt->fetchAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ if (!$handle) {
|
|||||||
jsonResponse(['error' => 'ไม่สามารถเปิดไฟล์ CSV ได้'], 500);
|
jsonResponse(['error' => 'ไม่สามารถเปิดไฟล์ CSV ได้'], 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check UTF-8 BOM or UTF-16
|
// Check UTF-8 BOM
|
||||||
$bom = fread($handle, 3);
|
$bom = fread($handle, 3);
|
||||||
if ($bom !== "\xEF\xBB\xBF") {
|
if ($bom !== "\xEF\xBB\xBF") {
|
||||||
rewind($handle);
|
rewind($handle);
|
||||||
@@ -61,7 +61,7 @@ $cleanHeaders = array_map(function($h) {
|
|||||||
|
|
||||||
// Fetch all questions for this survey
|
// Fetch all questions for this survey
|
||||||
$stmt = $db->prepare("
|
$stmt = $db->prepare("
|
||||||
SELECT q.id, q.question_text, q.question_type
|
SELECT q.id, q.question_text, q.question_type, q.options
|
||||||
FROM survey_questions q
|
FROM survey_questions q
|
||||||
JOIN survey_sections s ON q.section_id = s.id
|
JOIN survey_sections s ON q.section_id = s.id
|
||||||
WHERE s.survey_id = ?
|
WHERE s.survey_id = ?
|
||||||
@@ -75,16 +75,57 @@ if (empty($questions)) {
|
|||||||
jsonResponse(['error' => 'ไม่พบข้อคำถามในแบบสอบถามนี้'], 400);
|
jsonResponse(['error' => 'ไม่พบข้อคำถามในแบบสอบถามนี้'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map CSV Column Index -> question_id
|
// Helper: normalize text for comparison
|
||||||
$colToQuestionMap = [];
|
|
||||||
|
|
||||||
function normalizeText($str) {
|
function normalizeText($str) {
|
||||||
$str = mb_strtolower($str, 'UTF-8');
|
$str = mb_strtolower($str, 'UTF-8');
|
||||||
// Remove leading numbers or prefix like "1. ", "ข้อ 1. "
|
// Remove leading numbers or prefix like "1. ", "ข้อ 1. "
|
||||||
$str = preg_replace('/^[\d๑-๙]+[\.\)\s\-:\x{00A0}]*/u', '', $str);
|
$str = preg_replace('/^[\d๑-๙\.]*[\.\)\s\-:\x{00A0}]*/u', '', $str);
|
||||||
return trim($str);
|
return trim($str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to parse Likert rating values from numbers or Thai text
|
||||||
|
function parseRatingScore($str) {
|
||||||
|
$trimmed = trim((string)$str);
|
||||||
|
if ($trimmed === '') return null;
|
||||||
|
|
||||||
|
if (is_numeric($trimmed)) {
|
||||||
|
$num = (int)$trimmed;
|
||||||
|
return ($num >= 1 && $num <= 5) ? $num : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^([1-5])\s*[\.\:\-\(\s]/u', $trimmed, $m)) {
|
||||||
|
return (int)$m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb_strpos($trimmed, 'มากที่สุด') !== false || mb_strpos($trimmed, 'ดีมาก') !== false || mb_strpos($trimmed, 'ยอดเยี่ยม') !== false || mb_strpos($trimmed, 'ดีเยี่ยม') !== false || mb_strpos($trimmed, 'เห็นด้วยอย่างยิ่ง') !== false) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'น้อยที่สุด') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วยอย่างยิ่ง') !== false) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'มาก') !== false || mb_strpos($trimmed, 'ดี') !== false || mb_strpos($trimmed, 'เห็นด้วย') !== false) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'ปานกลาง') !== false || mb_strpos($trimmed, 'พอใช้') !== false || mb_strpos($trimmed, 'ไม่แน่ใจ') !== false) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'น้อย') !== false || mb_strpos($trimmed, 'ควรปรับปรุง') !== false || mb_strpos($trimmed, 'ต้องปรับปรุง') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วย') !== false) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'ปรับปรุง') !== false) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map CSV Column Index -> question_id
|
||||||
|
$colToQuestionMap = [];
|
||||||
|
$questionsById = [];
|
||||||
|
foreach ($questions as $q) {
|
||||||
|
$questionsById[$q['id']] = $q;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($cleanHeaders as $colIdx => $headerText) {
|
foreach ($cleanHeaders as $colIdx => $headerText) {
|
||||||
if (empty($headerText) || strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') {
|
if (empty($headerText) || strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') {
|
||||||
continue;
|
continue;
|
||||||
@@ -92,11 +133,22 @@ foreach ($cleanHeaders as $colIdx => $headerText) {
|
|||||||
|
|
||||||
$normHeader = normalizeText($headerText);
|
$normHeader = normalizeText($headerText);
|
||||||
|
|
||||||
|
// Extract inside bracket if Google Forms grid header e.g. "Heading [Question]"
|
||||||
|
$bracketText = '';
|
||||||
|
if (preg_match('/\[(.*?)\]/', $headerText, $m)) {
|
||||||
|
$bracketText = normalizeText($m[1]);
|
||||||
|
}
|
||||||
|
|
||||||
// Try exact or normalized matching against survey questions
|
// Try exact or normalized matching against survey questions
|
||||||
foreach ($questions as $q) {
|
foreach ($questions as $q) {
|
||||||
$normQText = normalizeText($q['question_text']);
|
$normQText = normalizeText($q['question_text']);
|
||||||
|
|
||||||
if ($normHeader === $normQText || strpos($normHeader, $normQText) !== false || strpos($normQText, $normHeader) !== false) {
|
if (
|
||||||
|
$normHeader === $normQText ||
|
||||||
|
($bracketText !== '' && ($bracketText === $normQText || strpos($bracketText, $normQText) !== false || strpos($normQText, $bracketText) !== false)) ||
|
||||||
|
strpos($normHeader, $normQText) !== false ||
|
||||||
|
strpos($normQText, $normHeader) !== false
|
||||||
|
) {
|
||||||
$colToQuestionMap[$colIdx] = $q['id'];
|
$colToQuestionMap[$colIdx] = $q['id'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -104,7 +156,7 @@ foreach ($cleanHeaders as $colIdx => $headerText) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: If column mapping count is low, map columns sequentially to questions
|
// Fallback: If column mapping count is low, map columns sequentially to questions
|
||||||
if (count($colToQuestionMap) === 0) {
|
if (count($colToQuestionMap) < count($questions) / 2) {
|
||||||
$qIdx = 0;
|
$qIdx = 0;
|
||||||
foreach ($cleanHeaders as $colIdx => $headerText) {
|
foreach ($cleanHeaders as $colIdx => $headerText) {
|
||||||
if (strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') continue;
|
if (strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') continue;
|
||||||
@@ -145,6 +197,15 @@ try {
|
|||||||
foreach ($colToQuestionMap as $colIdx => $qId) {
|
foreach ($colToQuestionMap as $colIdx => $qId) {
|
||||||
$val = isset($row[$colIdx]) ? trim($row[$colIdx]) : '';
|
$val = isset($row[$colIdx]) ? trim($row[$colIdx]) : '';
|
||||||
if ($val !== '') {
|
if ($val !== '') {
|
||||||
|
$targetQ = $questionsById[$qId] ?? null;
|
||||||
|
// Normalize rating score if it's a 5-point rating scale question
|
||||||
|
if ($targetQ && $targetQ['question_type'] === 'radio' && empty($targetQ['options'])) {
|
||||||
|
$score = parseRatingScore($val);
|
||||||
|
if ($score !== null) {
|
||||||
|
$val = (string)$score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$stmtAns = $db->prepare("INSERT INTO response_answers (response_id, question_id, answer_value) VALUES (?, ?, ?)");
|
$stmtAns = $db->prepare("INSERT INTO response_answers (response_id, question_id, answer_value) VALUES (?, ?, ?)");
|
||||||
$stmtAns->execute([$responseId, $qId, $val]);
|
$stmtAns->execute([$responseId, $qId, $val]);
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-15
@@ -28,6 +28,44 @@ if (!isAdmin()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to parse Likert rating values from numbers or Thai text
|
||||||
|
*/
|
||||||
|
function parseRatingScore($str) {
|
||||||
|
$trimmed = trim((string)$str);
|
||||||
|
if ($trimmed === '') return null;
|
||||||
|
|
||||||
|
if (is_numeric($trimmed)) {
|
||||||
|
$num = (int)$trimmed;
|
||||||
|
return ($num >= 1 && $num <= 5) ? $num : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^([1-5])\s*[\.\:\-\(\s]/u', $trimmed, $m)) {
|
||||||
|
return (int)$m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb_strpos($trimmed, 'มากที่สุด') !== false || mb_strpos($trimmed, 'ดีมาก') !== false || mb_strpos($trimmed, 'ยอดเยี่ยม') !== false || mb_strpos($trimmed, 'ดีเยี่ยม') !== false || mb_strpos($trimmed, 'เห็นด้วยอย่างยิ่ง') !== false) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'น้อยที่สุด') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วยอย่างยิ่ง') !== false) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'มาก') !== false || mb_strpos($trimmed, 'ดี') !== false || mb_strpos($trimmed, 'เห็นด้วย') !== false) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'ปานกลาง') !== false || mb_strpos($trimmed, 'พอใช้') !== false || mb_strpos($trimmed, 'ไม่แน่ใจ') !== false) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'น้อย') !== false || mb_strpos($trimmed, 'ควรปรับปรุง') !== false || mb_strpos($trimmed, 'ต้องปรับปรุง') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วย') !== false) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (mb_strpos($trimmed, 'ปรับปรุง') !== false) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'summary':
|
case 'summary':
|
||||||
getSummary($surveyId);
|
getSummary($surveyId);
|
||||||
@@ -52,24 +90,44 @@ function getSummary($surveyId) {
|
|||||||
$stmt->execute([$surveyId]);
|
$stmt->execute([$surveyId]);
|
||||||
$surveyInfo = $stmt->fetch();
|
$surveyInfo = $stmt->fetch();
|
||||||
|
|
||||||
// Get overall mean and SD for radio/rating questions
|
// Fetch all answers for rating questions in this survey and compute mean/sd
|
||||||
$stmt = $db->prepare("
|
$stmt = $db->prepare("
|
||||||
SELECT
|
SELECT ra.answer_value
|
||||||
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
|
FROM response_answers ra
|
||||||
JOIN survey_questions sq ON ra.question_id = sq.id
|
JOIN survey_questions sq ON ra.question_id = sq.id
|
||||||
JOIN survey_sections ss ON sq.section_id = ss.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]+$'
|
WHERE ss.survey_id = ? AND sq.question_type = 'radio' AND sq.options IS NULL
|
||||||
");
|
");
|
||||||
$stmt->execute([$surveyId]);
|
$stmt->execute([$surveyId]);
|
||||||
$overall = $stmt->fetch();
|
$rows = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$scores = [];
|
||||||
|
foreach ($rows as $ans) {
|
||||||
|
$sc = parseRatingScore($ans);
|
||||||
|
if ($sc !== null) {
|
||||||
|
$scores[] = $sc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$overallMean = 0;
|
||||||
|
$overallSd = 0;
|
||||||
|
|
||||||
|
if (count($scores) > 0) {
|
||||||
|
$overallMean = array_sum($scores) / count($scores);
|
||||||
|
if (count($scores) > 1) {
|
||||||
|
$sumSq = 0;
|
||||||
|
foreach ($scores as $s) {
|
||||||
|
$sumSq += pow($s - $overallMean, 2);
|
||||||
|
}
|
||||||
|
$overallSd = sqrt($sumSq / (count($scores) - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jsonResponse([
|
jsonResponse([
|
||||||
'survey' => $surveyInfo,
|
'survey' => $surveyInfo,
|
||||||
'total_responses' => (int)$total,
|
'total_responses' => (int)$total,
|
||||||
'overall_mean' => $overall['overall_mean'] ? round((float)$overall['overall_mean'], 2) : 0,
|
'overall_mean' => round($overallMean, 2),
|
||||||
'overall_sd' => $overall['overall_sd'] ? round((float)$overall['overall_sd'], 2) : 0
|
'overall_sd' => round($overallSd, 2)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,11 +172,11 @@ function getDetail($surveyId) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if ($q['question_type'] === 'radio' && empty($q['options'])) {
|
if ($q['question_type'] === 'radio' && empty($q['options'])) {
|
||||||
// Calculate mean, SD, frequency for rating questions (1-5)
|
// Rating scale question (1-5)
|
||||||
$aStmt = $db->prepare("
|
$aStmt = $db->prepare("
|
||||||
SELECT answer_value, COUNT(*) as cnt
|
SELECT answer_value, COUNT(*) as cnt
|
||||||
FROM response_answers
|
FROM response_answers
|
||||||
WHERE question_id = ? AND answer_value REGEXP '^[0-9]+$'
|
WHERE question_id = ? AND answer_value != ''
|
||||||
GROUP BY answer_value
|
GROUP BY answer_value
|
||||||
ORDER BY answer_value
|
ORDER BY answer_value
|
||||||
");
|
");
|
||||||
@@ -134,11 +192,14 @@ function getDetail($surveyId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($freqData as $fd) {
|
foreach ($freqData as $fd) {
|
||||||
$val = (int)$fd['answer_value'];
|
$rawVal = trim($fd['answer_value']);
|
||||||
|
$cnt = (int)$fd['cnt'];
|
||||||
|
$val = parseRatingScore($rawVal);
|
||||||
|
|
||||||
if ($val >= 1 && $val <= 5) {
|
if ($val >= 1 && $val <= 5) {
|
||||||
$frequencies[$val]['count'] = (int)$fd['cnt'];
|
$frequencies[$val]['count'] += $cnt;
|
||||||
$totalAnswers += (int)$fd['cnt'];
|
$totalAnswers += $cnt;
|
||||||
for ($j = 0; $j < (int)$fd['cnt']; $j++) {
|
for ($j = 0; $j < $cnt; $j++) {
|
||||||
$values[] = $val;
|
$values[] = $val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +254,7 @@ function getDetail($surveyId) {
|
|||||||
$aStmt = $db->prepare("
|
$aStmt = $db->prepare("
|
||||||
SELECT answer_value, COUNT(*) as cnt
|
SELECT answer_value, COUNT(*) as cnt
|
||||||
FROM response_answers
|
FROM response_answers
|
||||||
WHERE question_id = ?
|
WHERE question_id = ? AND answer_value != ''
|
||||||
GROUP BY answer_value
|
GROUP BY answer_value
|
||||||
ORDER BY cnt DESC
|
ORDER BY cnt DESC
|
||||||
");
|
");
|
||||||
|
|||||||
+29
-11
@@ -24,44 +24,62 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="css/style.css?v=6">
|
<link rel="stylesheet" href="css/style.css?v=9">
|
||||||
<style>
|
<style>
|
||||||
body { background-color: var(--bg-color); font-family: 'Noto Sans Thai', sans-serif; }
|
body { background-color: var(--bg-color); font-family: 'Noto Sans Thai', sans-serif; }
|
||||||
.stats-view-container {
|
.stats-view-container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 100px 20px 60px;
|
padding: 90px 16px 60px;
|
||||||
}
|
}
|
||||||
.stats-header {
|
.stats-header {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: var(--header-height);
|
min-height: var(--header-height);
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
border-bottom: 3px solid #fbbf24;
|
border-bottom: 3px solid #fbbf24;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 40px;
|
padding: 10px 24px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.stats-title {
|
.stats-title {
|
||||||
font-size: 1.25rem;
|
font-size: 1.15rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #1e3a8a;
|
color: #1e3a8a;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.stats-header {
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
.stats-title {
|
||||||
|
max-width: 220px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.stats-view-container {
|
||||||
|
padding: 120px 10px 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="font-thai">
|
<body class="font-thai">
|
||||||
<header class="stats-header">
|
<header class="stats-header">
|
||||||
<div class="d-flex items-center gap-16">
|
<div class="d-flex items-center gap-12 overflow-hidden">
|
||||||
<button class="btn btn-ghost btn-sm" onclick="window.close()">✕ ปิด</button>
|
<button class="btn btn-ghost btn-sm" onclick="window.close()">✕ <span class="hidden sm:inline">ปิด</span></button>
|
||||||
<div class="stats-title" id="statsSurveyTitle">กำลังโหลดผลสถิติ...</div>
|
<div class="stats-title" id="statsSurveyTitle">กำลังโหลดผลสถิติ...</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="statsActions" class="d-flex gap-12">
|
<div id="statsActions" class="d-flex gap-8 flex-wrap">
|
||||||
<!-- Buttons will be rendered here -->
|
<!-- Buttons will be rendered here -->
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -75,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js?v=9"></script>
|
||||||
<script src="js/stats.js"></script>
|
<script src="js/stats.js?v=9"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user