Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
+277
View File
@@ -0,0 +1,277 @@
<?php
require_once 'header.php';
require_once '../gdrive.php';
$action = $_GET['action'] ?? 'list';
$folder_id = getSetting($pdo, 'google_drive_folder_id');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_competition'])) {
$title = $_POST['title'];
$year_th = $_POST['year_th'];
$rank_result = $_POST['rank_result'];
$details = $_POST['details'];
$authors_str = $_POST['authors']; // comma separated
$stmt = $pdo->prepare("INSERT INTO competitions (title, details, rank_result, year_th) VALUES (?, ?, ?, ?)");
$stmt->execute([$title, $details, $rank_result, $year_th]);
$competition_id = $pdo->lastInsertId();
// Handle authors
$authors = array_filter(array_map('trim', explode(',', $authors_str)));
foreach ($authors as $author) {
$stmt = $pdo->prepare("INSERT INTO competition_authors (competition_id, author_name) VALUES (?, ?)");
$stmt->execute([$competition_id, $author]);
}
// Handle file uploads
if (!empty($_FILES['attachments']['name'][0])) {
foreach ($_FILES['attachments']['name'] as $key => $name) {
if ($_FILES['attachments']['error'][$key] == 0) {
$tmp_name = $_FILES['attachments']['tmp_name'][$key];
$gdrive_file_id = uploadFileToDrive($tmp_name, $name, $folder_id);
if ($gdrive_file_id) {
$stmt = $pdo->prepare("INSERT INTO competition_attachments (competition_id, file_name, gdrive_file_id) VALUES (?, ?, ?)");
$stmt->execute([$competition_id, $name, $gdrive_file_id]);
}
}
}
}
logActivity($pdo, 'CREATE_COMPETITION', "เพิ่มผลงาน: $title");
header("Location: competitions.php");
exit;
}
if (isset($_POST['edit_competition'])) {
$id = $_POST['id'];
$title = $_POST['title'];
$year_th = $_POST['year_th'];
$rank_result = $_POST['rank_result'];
$details = $_POST['details'];
$authors_str = $_POST['authors'];
$stmt = $pdo->prepare("UPDATE competitions SET title = ?, details = ?, rank_result = ?, year_th = ? WHERE id = ?");
$stmt->execute([$title, $details, $rank_result, $year_th, $id]);
// Update authors (delete all and re-insert)
$stmt = $pdo->prepare("DELETE FROM competition_authors WHERE competition_id = ?");
$stmt->execute([$id]);
$authors = array_filter(array_map('trim', explode(',', $authors_str)));
foreach ($authors as $author) {
$stmt = $pdo->prepare("INSERT INTO competition_authors (competition_id, author_name) VALUES (?, ?)");
$stmt->execute([$id, $author]);
}
// Handle new file uploads
if (!empty($_FILES['attachments']['name'][0])) {
foreach ($_FILES['attachments']['name'] as $key => $name) {
if ($_FILES['attachments']['error'][$key] == 0) {
$tmp_name = $_FILES['attachments']['tmp_name'][$key];
$gdrive_file_id = uploadFileToDrive($tmp_name, $name, $folder_id);
if ($gdrive_file_id) {
$stmt = $pdo->prepare("INSERT INTO competition_attachments (competition_id, file_name, gdrive_file_id) VALUES (?, ?, ?)");
$stmt->execute([$id, $name, $gdrive_file_id]);
}
}
}
}
logActivity($pdo, 'UPDATE_COMPETITION', "แก้ไขผลงาน: $title");
header("Location: competitions.php");
exit;
}
}
if ($action === 'delete_attachment' && isset($_GET['id'])) {
$id = $_GET['id'];
$stmt = $pdo->prepare("SELECT * FROM competition_attachments WHERE id = ?");
$stmt->execute([$id]);
$attachment = $stmt->fetch();
if ($attachment) {
deleteFileFromDrive($attachment['gdrive_file_id']);
$stmt = $pdo->prepare("DELETE FROM competition_attachments WHERE id = ?");
$stmt->execute([$id]);
logActivity($pdo, 'DELETE_COMPETITION_FILE', "ลบไฟล์แนบผลงาน: " . $attachment['file_name']);
}
header("Location: competitions.php?action=edit&id=" . $attachment['competition_id']);
exit;
}
if ($action === 'delete' && isset($_GET['id'])) {
$id = $_GET['id'];
$stmt = $pdo->prepare("SELECT title FROM competitions WHERE id = ?");
$stmt->execute([$id]);
$comp = $stmt->fetch();
if ($comp) {
// Delete all attachments from GDrive
$stmt = $pdo->prepare("SELECT * FROM competition_attachments WHERE competition_id = ?");
$stmt->execute([$id]);
$attachments = $stmt->fetchAll();
foreach ($attachments as $attachment) {
deleteFileFromDrive($attachment['gdrive_file_id']);
}
$stmt = $pdo->prepare("DELETE FROM competitions WHERE id = ?");
$stmt->execute([$id]);
logActivity($pdo, 'DELETE_COMPETITION', "ลบผลงาน: " . $comp['title']);
}
header("Location: competitions.php");
exit;
}
?>
<div class="mb-8 flex justify-between items-center">
<h1 class="text-3xl font-bold text-gray-900">จัดการผลงานประกวด</h1>
<?php if ($action === 'list'): ?>
<a href="competitions.php?action=add" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md font-medium transition-colors">
+ เพิ่มผลงานใหม่
</a>
<?php else: ?>
<a href="competitions.php" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md font-medium transition-colors">
กลับหน้ารายการ
</a>
<?php endif; ?>
</div>
<?php if ($action === 'list'): ?>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<table class="datatable w-full text-left border-collapse">
<thead>
<tr>
<th class="border-b py-3 px-4 bg-gray-50">ปี (พ.ศ.)</th>
<th class="border-b py-3 px-4 bg-gray-50">ชื่อผลงาน</th>
<th class="border-b py-3 px-4 bg-gray-50">รางวัล</th>
<th class="border-b py-3 px-4 bg-gray-50">ผู้จัดทำ</th>
<th class="border-b py-3 px-4 bg-gray-50">ไฟล์แนบ</th>
<th class="border-b py-3 px-4 bg-gray-50">จัดการ</th>
</tr>
</thead>
<tbody>
<?php
$stmt = $pdo->query("
SELECT c.*,
(SELECT GROUP_CONCAT(author_name SEPARATOR ', ') FROM competition_authors WHERE competition_id = c.id) as authors,
(SELECT COUNT(*) FROM competition_attachments WHERE competition_id = c.id) as file_count
FROM competitions c
ORDER BY c.year_th DESC, c.id DESC
");
while ($row = $stmt->fetch()):
?>
<tr class="hover:bg-gray-50">
<td class="border-b py-3 px-4 text-center"><?= $row['year_th'] ?></td>
<td class="border-b py-3 px-4"><?= htmlspecialchars((string)$row['title']) ?></td>
<td class="border-b py-3 px-4">
<span class="inline-block px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-md">
<?= htmlspecialchars((string)$row['rank_result']) ?>
</span>
</td>
<td class="border-b py-3 px-4 text-sm"><?= htmlspecialchars((string)$row['authors']) ?></td>
<td class="border-b py-3 px-4 text-center"><?= $row['file_count'] ?></td>
<td class="border-b py-3 px-4">
<a href="competitions.php?action=edit&id=<?= $row['id'] ?>" class="text-blue-600 hover:underline mr-3">แก้ไข</a>
<a href="competitions.php?action=delete&id=<?= $row['id'] ?>" class="text-red-600 hover:underline" onclick="return confirm('ยืนยันการลบข้อมูลนี้? การลบจะลบไฟล์ใน Google Drive ด้วย')">ลบ</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
<?php elseif ($action === 'add' || $action === 'edit'):
$comp = null;
$attachments = [];
$authors_str = '';
if ($action === 'edit' && isset($_GET['id'])) {
$stmt = $pdo->prepare("SELECT * FROM competitions WHERE id = ?");
$stmt->execute([$_GET['id']]);
$comp = $stmt->fetch();
$stmt = $pdo->prepare("SELECT * FROM competition_attachments WHERE competition_id = ?");
$stmt->execute([$_GET['id']]);
$attachments = $stmt->fetchAll();
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(author_name SEPARATOR ', ') FROM competition_authors WHERE competition_id = ?");
$stmt->execute([$_GET['id']]);
$authors_str = $stmt->fetchColumn();
}
?>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<form action="competitions.php" method="POST" enctype="multipart/form-data" id="compForm">
<?php if ($comp): ?>
<input type="hidden" name="edit_competition" value="1">
<input type="hidden" name="id" value="<?= $comp['id'] ?>">
<?php else: ?>
<input type="hidden" name="add_competition" value="1">
<?php endif; ?>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">ชื่อผลงาน</label>
<input type="text" name="title" required class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-green-500" value="<?= $comp ? htmlspecialchars((string)$comp['title']) : '' ?>">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">ปี (พ.ศ.)</label>
<input type="number" name="year_th" required class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-green-500" value="<?= $comp ? $comp['year_th'] : (date('Y') + 543) ?>">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">รางวัลที่ได้รับ (เช่น รางวัลที่ 1, ชมเชย)</label>
<input type="text" name="rank_result" required class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-green-500" value="<?= $comp ? htmlspecialchars((string)$comp['rank_result']) : '' ?>">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">รายชื่อผู้จัดทำ (คั่นด้วยเครื่องหมายจุลภาค , )</label>
<input type="text" name="authors" class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-green-500" value="<?= htmlspecialchars((string)$authors_str) ?>" placeholder="นายเอ, นายบี, นางสาวซี">
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">รายละเอียด</label>
<input type="hidden" name="details" id="detailsInput">
<div id="editor" style="height: 200px;"><?= $comp ? $comp['details'] : '' ?></div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">อัปโหลดไฟล์แนบ (PDF)</label>
<input type="file" name="attachments[]" multiple accept=".pdf" class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-green-500">
<?php if (!empty($attachments)): ?>
<div class="mt-4">
<h4 class="text-sm font-medium text-gray-700 mb-2">ไฟล์แนบปัจจุบัน</h4>
<ul class="space-y-2">
<?php foreach ($attachments as $file): ?>
<li class="flex items-center justify-between bg-gray-50 p-3 rounded border">
<span class="text-gray-700"><?= htmlspecialchars((string)$file['file_name']) ?></span>
<a href="competitions.php?action=delete_attachment&id=<?= $file['id'] ?>" class="text-red-500 hover:text-red-700 text-sm" onclick="return confirm('ยืนยันการลบไฟล์นี้จากระบบและ Google Drive?')">ลบไฟล์</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
<div>
<button type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded focus:outline-none transition-colors">
<?= $comp ? 'บันทึกการแก้ไข' : 'เพิ่มผลงาน' ?>
</button>
</div>
</form>
</div>
<script>
var quill = new Quill('#editor', {
theme: 'snow'
});
document.getElementById('compForm').onsubmit = function() {
document.getElementById('detailsInput').value = quill.root.innerHTML;
};
</script>
<?php endif; ?>
<?php require_once 'footer.php'; ?>
+75
View File
@@ -0,0 +1,75 @@
<?php
require_once 'header.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['insert_dummy'])) {
for ($i = 1; $i <= 10; $i++) {
// Meeting Dummy
$stmt = $pdo->prepare("INSERT INTO meeting_minutes (title, meeting_date, details) VALUES (?, ?, ?)");
$stmt->execute(["การประชุมคณะกรรมการ P4Q ครั้งที่ $i/2569", date('Y-m-d', strtotime("-$i days")), "<p>รายละเอียดการประชุมครั้งที่ $i</p>"]);
// Competition Dummy
$year = 2569 - ($i % 3);
$stmt = $pdo->prepare("INSERT INTO competitions (title, details, rank_result, year_th) VALUES (?, ?, ?, ?)");
$stmt->execute(["ผลงานพัฒนาคุณภาพ เรื่องที่ $i", "<p>รายละเอียดผลงานพัฒนาคุณภาพเรื่องที่ $i</p>", "รางวัลที่ " . rand(1, 3), $year]);
$comp_id = $pdo->lastInsertId();
// Authors Dummy
$stmt = $pdo->prepare("INSERT INTO competition_authors (competition_id, author_name) VALUES (?, ?)");
$stmt->execute([$comp_id, "นายแพทย์ทดสอบ คนที่ $i"]);
}
logActivity($pdo, 'INSERT_DUMMY', "เพิ่มข้อมูลตัวอย่าง 10 รายการ");
$success = "เพิ่มข้อมูลตัวอย่าง 10 รายการสำเร็จ";
}
if (isset($_POST['clear_dummy'])) {
// We will just clear all tables except settings
$pdo->query("SET FOREIGN_KEY_CHECKS = 0;");
$pdo->query("TRUNCATE TABLE meeting_attachments;");
$pdo->query("TRUNCATE TABLE meeting_minutes;");
$pdo->query("TRUNCATE TABLE competition_attachments;");
$pdo->query("TRUNCATE TABLE competition_authors;");
$pdo->query("TRUNCATE TABLE competitions;");
$pdo->query("SET FOREIGN_KEY_CHECKS = 1;");
logActivity($pdo, 'CLEAR_DUMMY', "ล้างข้อมูลทั้งหมด");
$success = "ล้างข้อมูลทั้งหมดสำเร็จ";
}
}
?>
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">จัดการข้อมูลตัวอย่าง</h1>
</div>
<?php if (!empty($success)): ?>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
<span class="block sm:inline"><?= $success ?></span>
</div>
<?php endif; ?>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 text-center">
<h2 class="text-xl font-bold mb-4 text-blue-600">เพิ่มข้อมูลตัวอย่าง</h2>
<p class="text-gray-600 mb-6">สร้างข้อมูลบันทึกการประชุม 10 รายการ และผลงานประกวด 10 รายการ</p>
<form action="" method="POST">
<input type="hidden" name="insert_dummy" value="1">
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full">
สร้างข้อมูลตัวอย่าง
</button>
</form>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 text-center">
<h2 class="text-xl font-bold mb-4 text-red-600">ล้างข้อมูลทั้งหมด</h2>
<p class="text-gray-600 mb-6">ลบข้อมูลบันทึกการประชุม ผลงาน และไฟล์แนบทั้งหมดในระบบ</p>
<form action="" method="POST" onsubmit="return confirm('ยืนยันการล้างข้อมูลทั้งหมด? ข้อมูลจะไม่สามารถกู้คืนได้');">
<input type="hidden" name="clear_dummy" value="1">
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded w-full">
ล้างข้อมูล
</button>
</form>
</div>
</div>
<?php require_once 'footer.php'; ?>
+23
View File
@@ -0,0 +1,23 @@
<?php // admin/footer.php ?>
<?php if ($current_page !== 'login.php'): ?>
</div>
</div>
</div>
<?php endif; ?>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
<script>
$(document).ready(function() {
if ($('.datatable').length) {
$('.datatable').DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
}
});
}
});
</script>
</body>
</html>
+57
View File
@@ -0,0 +1,57 @@
<?php
// admin/header.php
session_start();
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../db.php';
// Check auth
$current_page = basename($_SERVER['PHP_SELF']);
if ($current_page !== 'login.php' && empty($_SESSION['admin_logged_in'])) {
header("Location: login.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>P4Q Admin Panel</title>
<link rel="icon" type="image/png" href="../logo.png">
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<!-- DataTables -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
<!-- Quill.js for Text Editor -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
body { font-family: 'Sarabun', sans-serif; background-color: #f3f4f6; }
.glass { background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.3); }
</style>
</head>
<body class="text-gray-800">
<?php if ($current_page !== 'login.php'): ?>
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<aside class="w-64 bg-white shadow-md flex-shrink-0">
<div class="p-6 text-center">
<img src="../logo.png" alt="Logo" class="h-16 mx-auto mb-2" onerror="this.src='https://via.placeholder.com/64'">
<h1 class="text-xl font-bold text-green-700">P4Q Admin</h1>
</div>
<nav class="mt-6">
<a href="index.php" class="block px-6 py-3 hover:bg-gray-100 <?= $current_page == 'index.php' ? 'bg-gray-100 font-semibold' : '' ?>">ภาพรวมระบบ (Dashboard)</a>
<a href="meetings.php" class="block px-6 py-3 hover:bg-gray-100 <?= $current_page == 'meetings.php' ? 'bg-gray-100 font-semibold' : '' ?>">บันทึกการประชุม</a>
<a href="competitions.php" class="block px-6 py-3 hover:bg-gray-100 <?= $current_page == 'competitions.php' ? 'bg-gray-100 font-semibold' : '' ?>">ผลงานประกวด</a>
<a href="settings.php" class="block px-6 py-3 hover:bg-gray-100 <?= $current_page == 'settings.php' ? 'bg-gray-100 font-semibold' : '' ?>">ตั้งค่า Google Drive</a>
<a href="logs.php" class="block px-6 py-3 hover:bg-gray-100 <?= $current_page == 'logs.php' ? 'bg-gray-100 font-semibold' : '' ?>">ประวัติการทำงาน</a>
<a href="dummy.php" class="block px-6 py-3 hover:bg-gray-100 text-yellow-600 <?= $current_page == 'dummy.php' ? 'bg-gray-100 font-semibold' : '' ?>">จัดการข้อมูลตัวอย่าง</a>
<a href="logout.php" class="block px-6 py-3 hover:bg-gray-100 text-red-600">ออกจากระบบ</a>
</nav>
</aside>
<!-- Main Content -->
<div class="flex-1 overflow-y-auto p-8">
<div class="max-w-6xl mx-auto">
<?php endif; ?>
+55
View File
@@ -0,0 +1,55 @@
<?php
require_once 'header.php';
$total_meetings = $pdo ? $pdo->query("SELECT COUNT(*) FROM meeting_minutes")->fetchColumn() : 0;
$total_competitions = $pdo ? $pdo->query("SELECT COUNT(*) FROM competitions")->fetchColumn() : 0;
$total_logs = $pdo ? $pdo->query("SELECT COUNT(*) FROM activity_logs")->fetchColumn() : 0;
?>
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">ภาพรวมระบบ (Dashboard)</h1>
<p class="text-gray-600 mt-2">ยินดีต้อนรับเข้าสู่ระบบจัดการข้อมูล P4Q</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex items-center space-x-4">
<div class="p-3 bg-blue-100 text-blue-600 rounded-full">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"></path></svg>
</div>
<div>
<p class="text-sm text-gray-500 font-medium">บันทึกการประชุมทั้งหมด</p>
<p class="text-2xl font-bold text-gray-900"><?= number_format((float)$total_meetings) ?> รายการ</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex items-center space-x-4">
<div class="p-3 bg-green-100 text-green-600 rounded-full">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path></svg>
</div>
<div>
<p class="text-sm text-gray-500 font-medium">ผลงานประกวดทั้งหมด</p>
<p class="text-2xl font-bold text-gray-900"><?= number_format((float)$total_competitions) ?> รายการ</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex items-center space-x-4">
<div class="p-3 bg-purple-100 text-purple-600 rounded-full">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</div>
<div>
<p class="text-sm text-gray-500 font-medium">ประวัติการทำงาน</p>
<p class="text-2xl font-bold text-gray-900"><?= number_format((float)$total_logs) ?> รายการ</p>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-lg font-bold text-gray-800 mb-4">ข้อมูลเบื้องต้น</h2>
<ul class="list-disc pl-5 space-y-2 text-gray-600">
<li>เพื่อความปลอดภัย รหัสผ่านหลังบ้านจะถูกกำหนดไว้ในไฟล์ <code>config.php</code></li>
<li>ไฟล์ที่อัปโหลดจะถูกส่งไปยัง Google Drive ตาม Folder ID ที่ตั้งค่าไว้ในเมนู <a href="settings.php" class="text-blue-500 underline">ตั้งค่า Google Drive</a></li>
<li>หากมีการลบข้อมูล ไฟล์ใน Google Drive ก็จะถูกลบออกด้วย</li>
</ul>
</div>
<?php require_once 'footer.php'; ?>
+46
View File
@@ -0,0 +1,46 @@
<?php
session_start();
require_once __DIR__ . '/../config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$password = $_POST['password'] ?? '';
if ($password === ADMIN_PASSWORD) {
$_SESSION['admin_logged_in'] = true;
header("Location: index.php");
exit;
} else {
$error = "รหัสผ่านไม่ถูกต้อง";
}
}
?>
<?php require_once 'header.php'; ?>
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')]">
<div class="max-w-md w-full space-y-8 glass p-10 rounded-2xl shadow-xl">
<div>
<img class="mx-auto h-24 w-auto" src="../logo.png" alt="Logo" onerror="this.src='https://via.placeholder.com/150'">
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
เข้าสู่ระบบจัดการ P4Q
</h2>
</div>
<form class="mt-8 space-y-6" action="login.php" method="POST">
<input type="hidden" name="remember" value="true">
<div class="rounded-md shadow-sm -space-y-px">
<div>
<label for="password" class="sr-only">รหัสผ่าน</label>
<input id="password" name="password" type="password" required class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm" placeholder="กรอกรหัสผ่านเพื่อเข้าใช้งาน">
</div>
</div>
<?php if (!empty($error)): ?>
<div class="text-red-500 text-sm text-center font-medium"><?= $error ?></div>
<?php endif; ?>
<div>
<button type="submit" class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all">
เข้าสู่ระบบ
</button>
</div>
</form>
</div>
</div>
<?php require_once 'footer.php'; ?>
+5
View File
@@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header("Location: login.php");
exit;
+38
View File
@@ -0,0 +1,38 @@
<?php
require_once 'header.php';
$stmt = $pdo->prepare("SELECT * FROM activity_logs ORDER BY created_at DESC");
$stmt->execute();
$logs = $stmt->fetchAll();
?>
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">ประวัติการทำงาน</h1>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<table class="datatable w-full text-left border-collapse">
<thead>
<tr>
<th class="border-b py-3 px-4 bg-gray-50">เวลา</th>
<th class="border-b py-3 px-4 bg-gray-50">ประเภทการทำงาน</th>
<th class="border-b py-3 px-4 bg-gray-50">รายละเอียด</th>
</tr>
</thead>
<tbody>
<?php foreach ($logs as $log): ?>
<tr class="hover:bg-gray-50">
<td class="border-b py-3 px-4"><?= date('d/m/Y H:i:s', strtotime($log['created_at'])) ?></td>
<td class="border-b py-3 px-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
<?= htmlspecialchars((string)$log['action_type']) ?>
</span>
</td>
<td class="border-b py-3 px-4"><?= htmlspecialchars((string)$log['action_details']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require_once 'footer.php'; ?>
+232
View File
@@ -0,0 +1,232 @@
<?php
require_once 'header.php';
require_once '../gdrive.php';
$action = $_GET['action'] ?? 'list';
$folder_id = getSetting($pdo, 'google_drive_folder_id');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_meeting'])) {
$title = $_POST['title'];
$meeting_date = $_POST['meeting_date'];
$details = $_POST['details'];
$stmt = $pdo->prepare("INSERT INTO meeting_minutes (title, meeting_date, details) VALUES (?, ?, ?)");
$stmt->execute([$title, $meeting_date, $details]);
$meeting_id = $pdo->lastInsertId();
// Handle file uploads
if (!empty($_FILES['attachments']['name'][0])) {
foreach ($_FILES['attachments']['name'] as $key => $name) {
if ($_FILES['attachments']['error'][$key] == 0) {
$tmp_name = $_FILES['attachments']['tmp_name'][$key];
$gdrive_file_id = uploadFileToDrive($tmp_name, $name, $folder_id);
if ($gdrive_file_id) {
$stmt = $pdo->prepare("INSERT INTO meeting_attachments (meeting_id, file_name, gdrive_file_id) VALUES (?, ?, ?)");
$stmt->execute([$meeting_id, $name, $gdrive_file_id]);
}
}
}
}
logActivity($pdo, 'CREATE_MEETING', "เพิ่มบันทึกการประชุม: $title");
header("Location: meetings.php");
exit;
}
if (isset($_POST['edit_meeting'])) {
$id = $_POST['id'];
$title = $_POST['title'];
$meeting_date = $_POST['meeting_date'];
$details = $_POST['details'];
$stmt = $pdo->prepare("UPDATE meeting_minutes SET title = ?, meeting_date = ?, details = ? WHERE id = ?");
$stmt->execute([$title, $meeting_date, $details, $id]);
// Handle new file uploads
if (!empty($_FILES['attachments']['name'][0])) {
foreach ($_FILES['attachments']['name'] as $key => $name) {
if ($_FILES['attachments']['error'][$key] == 0) {
$tmp_name = $_FILES['attachments']['tmp_name'][$key];
$gdrive_file_id = uploadFileToDrive($tmp_name, $name, $folder_id);
if ($gdrive_file_id) {
$stmt = $pdo->prepare("INSERT INTO meeting_attachments (meeting_id, file_name, gdrive_file_id) VALUES (?, ?, ?)");
$stmt->execute([$id, $name, $gdrive_file_id]);
}
}
}
}
logActivity($pdo, 'UPDATE_MEETING', "แก้ไขบันทึกการประชุม: $title");
header("Location: meetings.php");
exit;
}
}
if ($action === 'delete_attachment' && isset($_GET['id'])) {
$id = $_GET['id'];
$stmt = $pdo->prepare("SELECT * FROM meeting_attachments WHERE id = ?");
$stmt->execute([$id]);
$attachment = $stmt->fetch();
if ($attachment) {
deleteFileFromDrive($attachment['gdrive_file_id']);
$stmt = $pdo->prepare("DELETE FROM meeting_attachments WHERE id = ?");
$stmt->execute([$id]);
logActivity($pdo, 'DELETE_MEETING_FILE', "ลบไฟล์แนบการประชุม: " . $attachment['file_name']);
}
header("Location: meetings.php?action=edit&id=" . $attachment['meeting_id']);
exit;
}
if ($action === 'delete' && isset($_GET['id'])) {
$id = $_GET['id'];
// Get meeting info
$stmt = $pdo->prepare("SELECT title FROM meeting_minutes WHERE id = ?");
$stmt->execute([$id]);
$meeting = $stmt->fetch();
if ($meeting) {
// Delete all attachments from GDrive
$stmt = $pdo->prepare("SELECT * FROM meeting_attachments WHERE meeting_id = ?");
$stmt->execute([$id]);
$attachments = $stmt->fetchAll();
foreach ($attachments as $attachment) {
deleteFileFromDrive($attachment['gdrive_file_id']);
}
$stmt = $pdo->prepare("DELETE FROM meeting_minutes WHERE id = ?");
$stmt->execute([$id]);
logActivity($pdo, 'DELETE_MEETING', "ลบบันทึกการประชุม: " . $meeting['title']);
}
header("Location: meetings.php");
exit;
}
?>
<div class="mb-8 flex justify-between items-center">
<h1 class="text-3xl font-bold text-gray-900">จัดการบันทึกการประชุม</h1>
<?php if ($action === 'list'): ?>
<a href="meetings.php?action=add" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md font-medium transition-colors">
+ เพิ่มบันทึกการประชุม
</a>
<?php else: ?>
<a href="meetings.php" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md font-medium transition-colors">
กลับหน้ารายการ
</a>
<?php endif; ?>
</div>
<?php if ($action === 'list'): ?>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<table class="datatable w-full text-left border-collapse">
<thead>
<tr>
<th class="border-b py-3 px-4 bg-gray-50">วันที่ประชุม</th>
<th class="border-b py-3 px-4 bg-gray-50">เรื่อง</th>
<th class="border-b py-3 px-4 bg-gray-50">จำนวนไฟล์แนบ</th>
<th class="border-b py-3 px-4 bg-gray-50">จัดการ</th>
</tr>
</thead>
<tbody>
<?php
$stmt = $pdo->query("
SELECT m.*, COUNT(a.id) as file_count
FROM meeting_minutes m
LEFT JOIN meeting_attachments a ON m.id = a.meeting_id
GROUP BY m.id
ORDER BY m.meeting_date DESC
");
while ($row = $stmt->fetch()):
?>
<tr class="hover:bg-gray-50">
<td class="border-b py-3 px-4"><?= date('d/m/Y', strtotime($row['meeting_date'])) ?></td>
<td class="border-b py-3 px-4"><?= htmlspecialchars((string)$row['title']) ?></td>
<td class="border-b py-3 px-4"><?= $row['file_count'] ?> ไฟล์</td>
<td class="border-b py-3 px-4">
<a href="meetings.php?action=edit&id=<?= $row['id'] ?>" class="text-blue-600 hover:underline mr-3">แก้ไข</a>
<a href="meetings.php?action=delete&id=<?= $row['id'] ?>" class="text-red-600 hover:underline" onclick="return confirm('ยืนยันการลบข้อมูลนี้? การลบจะลบไฟล์ใน Google Drive ด้วย')">ลบ</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
<?php elseif ($action === 'add' || $action === 'edit'):
$meeting = null;
$attachments = [];
if ($action === 'edit' && isset($_GET['id'])) {
$stmt = $pdo->prepare("SELECT * FROM meeting_minutes WHERE id = ?");
$stmt->execute([$_GET['id']]);
$meeting = $stmt->fetch();
$stmt = $pdo->prepare("SELECT * FROM meeting_attachments WHERE meeting_id = ?");
$stmt->execute([$_GET['id']]);
$attachments = $stmt->fetchAll();
}
?>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<form action="meetings.php" method="POST" enctype="multipart/form-data" id="meetingForm">
<?php if ($meeting): ?>
<input type="hidden" name="edit_meeting" value="1">
<input type="hidden" name="id" value="<?= $meeting['id'] ?>">
<?php else: ?>
<input type="hidden" name="add_meeting" value="1">
<?php endif; ?>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">วันที่ประชุม</label>
<input type="date" name="meeting_date" required class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" value="<?= $meeting ? $meeting['meeting_date'] : date('Y-m-d') ?>">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">เรื่อง</label>
<input type="text" name="title" required class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" value="<?= $meeting ? htmlspecialchars((string)$meeting['title']) : '' ?>">
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">รายละเอียด</label>
<input type="hidden" name="details" id="detailsInput">
<div id="editor" style="height: 200px;"><?= $meeting ? $meeting['details'] : '' ?></div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">อัปโหลดไฟล์แนบ (PDF)</label>
<input type="file" name="attachments[]" multiple accept=".pdf" class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
<?php if (!empty($attachments)): ?>
<div class="mt-4">
<h4 class="text-sm font-medium text-gray-700 mb-2">ไฟล์แนบปัจจุบัน</h4>
<ul class="space-y-2">
<?php foreach ($attachments as $file): ?>
<li class="flex items-center justify-between bg-gray-50 p-3 rounded border">
<span class="text-gray-700"><?= htmlspecialchars((string)$file['file_name']) ?></span>
<a href="meetings.php?action=delete_attachment&id=<?= $file['id'] ?>" class="text-red-500 hover:text-red-700 text-sm" onclick="return confirm('ยืนยันการลบไฟล์นี้จากระบบและ Google Drive?')">ลบไฟล์</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
<div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded focus:outline-none transition-colors">
<?= $meeting ? 'บันทึกการแก้ไข' : 'เพิ่มข้อมูล' ?>
</button>
</div>
</form>
</div>
<script>
var quill = new Quill('#editor', {
theme: 'snow'
});
document.getElementById('meetingForm').onsubmit = function() {
document.getElementById('detailsInput').value = quill.root.innerHTML;
};
</script>
<?php endif; ?>
<?php require_once 'footer.php'; ?>
+53
View File
@@ -0,0 +1,53 @@
<?php
require_once 'header.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$folder_id = $_POST['google_drive_folder_id'] ?? '';
// Check if exists
$stmt = $pdo->prepare("SELECT COUNT(*) FROM settings WHERE setting_key = 'google_drive_folder_id'");
$stmt->execute();
$exists = $stmt->fetchColumn();
if ($exists) {
$stmt = $pdo->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'google_drive_folder_id'");
$stmt->execute([$folder_id]);
} else {
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES ('google_drive_folder_id', ?)");
$stmt->execute([$folder_id]);
}
logActivity($pdo, 'UPDATE_SETTINGS', "อัปเดต Google Drive Folder ID");
$success = "บันทึกการตั้งค่าเรียบร้อยแล้ว";
}
$current_folder_id = getSetting($pdo, 'google_drive_folder_id');
?>
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900">ตั้งค่าระบบ</h1>
</div>
<?php if (!empty($success)): ?>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
<span class="block sm:inline"><?= $success ?></span>
</div>
<?php endif; ?>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 max-w-2xl">
<form action="" method="POST">
<div class="mb-4">
<label for="google_drive_folder_id" class="block text-sm font-medium text-gray-700 mb-2">Google Drive Folder ID</label>
<input type="text" id="google_drive_folder_id" name="google_drive_folder_id" value="<?= htmlspecialchars((string)$current_folder_id) ?>" class="appearance-none border border-gray-300 rounded-md w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-green-500" placeholder="เช่น 1A2b3C4d5E6f7G8h9I0j">
<p class="text-sm text-gray-500 mt-2">หมายเหตุ: ต้องแชร์โฟลเดอร์ใน Google Drive ให้กับอีเมล Service Account ของคุณด้วย เพื่อให้ระบบสามารถอัปโหลดไฟล์เข้าไปได้</p>
</div>
<div class="mt-6">
<button type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition-colors">
บันทึกการตั้งค่า
</button>
</div>
</form>
</div>
<?php require_once 'footer.php'; ?>
+12
View File
@@ -0,0 +1,12 @@
<?php
// config.php
define('DB_HOST', 'localhost');
define('DB_USER', 'root'); // สามารถเปลี่ยนตามระบบจริง
define('DB_PASS', '@Samui@10742'); // สามารถเปลี่ยนตามระบบจริง
define('DB_NAME', 'ksh_p4q');
// รหัสผ่านสำหรับเข้าใช้งานระบบหลังบ้าน
define('ADMIN_PASSWORD', '1074210742');
// ที่อยู่ของไฟล์ Service Account สำหรับ Google Drive API
define('GDRIVE_CREDENTIALS_PATH', __DIR__ . '/credentials.json');
+55
View File
@@ -0,0 +1,55 @@
CREATE TABLE IF NOT EXISTS `settings` (
`setting_key` VARCHAR(50) PRIMARY KEY,
`setting_value` TEXT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO `settings` (`setting_key`, `setting_value`) VALUES ('google_drive_folder_id', '');
CREATE TABLE IF NOT EXISTS `meeting_minutes` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`meeting_date` DATE NOT NULL,
`title` VARCHAR(255) NOT NULL,
`details` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `meeting_attachments` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`meeting_id` INT NOT NULL,
`file_name` VARCHAR(255) NOT NULL,
`gdrive_file_id` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`meeting_id`) REFERENCES `meeting_minutes`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `competitions` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL,
`details` TEXT,
`rank_result` VARCHAR(50),
`year_th` INT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `competition_authors` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`competition_id` INT NOT NULL,
`author_name` VARCHAR(255) NOT NULL,
FOREIGN KEY (`competition_id`) REFERENCES `competitions`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `competition_attachments` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`competition_id` INT NOT NULL,
`file_name` VARCHAR(255) NOT NULL,
`gdrive_file_id` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`competition_id`) REFERENCES `competitions`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `activity_logs` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`action_type` VARCHAR(50) NOT NULL,
`action_details` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+26
View File
@@ -0,0 +1,26 @@
<?php
// db.php
require_once __DIR__ . '/config.php';
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// สำหรับการตั้งค่าเริ่มต้น ถ้ายังไม่มี DB
// die("Database connection failed: " . $e->getMessage());
$pdo = null;
}
function logActivity($pdo, $action, $details) {
if (!$pdo) return;
$stmt = $pdo->prepare("INSERT INTO activity_logs (action_type, action_details) VALUES (?, ?)");
$stmt->execute([$action, $details]);
}
function getSetting($pdo, $key) {
if (!$pdo) return '';
$stmt = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = ?");
$stmt->execute([$key]);
return $stmt->fetchColumn();
}
+70
View File
@@ -0,0 +1,70 @@
<?php
// gdrive.php
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/config.php';
function getGoogleDriveClient() {
if (!class_exists('\Google\Client')) {
return null;
}
$client = new \Google\Client();
$client->setApplicationName('P4Q System');
$client->setScopes([\Google\Service\Drive::DRIVE_FILE]);
if (file_exists(GDRIVE_CREDENTIALS_PATH)) {
try {
$client->setAuthConfig(GDRIVE_CREDENTIALS_PATH);
return $client;
} catch (Exception $e) {
return null;
}
}
return null;
}
function uploadFileToDrive($filePath, $fileName, $folderId) {
$client = getGoogleDriveClient();
if (!$client || empty($folderId)) {
// Mock upload if no credentials or folder ID
return "mock_file_id_" . uniqid();
}
$service = new \Google\Service\Drive($client);
$fileMetadata = new \Google\Service\Drive\DriveFile([
'name' => $fileName,
'parents' => [$folderId]
]);
$content = file_get_contents($filePath);
try {
$file = $service->files->create($fileMetadata, [
'data' => $content,
'mimeType' => mime_content_type($filePath),
'uploadType' => 'multipart',
'fields' => 'id'
]);
return $file->id;
} catch (Exception $e) {
return "mock_file_id_err_" . uniqid();
}
}
function deleteFileFromDrive($fileId) {
if (strpos($fileId, 'mock_file_id') === 0) {
return true;
}
$client = getGoogleDriveClient();
if (!$client) {
return true;
}
$service = new \Google\Service\Drive($client);
try {
$service->files->delete($fileId);
return true;
} catch (Exception $e) {
return false;
}
}
+358
View File
@@ -0,0 +1,358 @@
<?php
require_once 'config.php';
require_once 'db.php';
$page = $_GET['page'] ?? 'competitions';
$selected_year = $_GET['year'] ?? '';
// Variables to hold data
$competitions = [];
$meetings = [];
$years = [];
if ($page === 'competitions') {
// Build query for competitions
$comp_query = "SELECT c.*,
(SELECT GROUP_CONCAT(author_name SEPARATOR ', ') FROM competition_authors WHERE competition_id = c.id) as authors
FROM competitions c";
if (!empty($selected_year)) {
$comp_query .= " WHERE c.year_th = :year";
}
$comp_query .= " ORDER BY c.year_th DESC, c.id DESC";
$comp_stmt = $pdo->prepare($comp_query);
if (!empty($selected_year)) {
$comp_stmt->bindParam(':year', $selected_year, PDO::PARAM_INT);
}
$comp_stmt->execute();
$competitions = $comp_stmt->fetchAll();
// Get unique years for filter
$years_stmt = $pdo->query("SELECT DISTINCT year_th FROM competitions ORDER BY year_th DESC");
$years = $years_stmt->fetchAll(PDO::FETCH_COLUMN);
} elseif ($page === 'meetings') {
// Build query for meetings
$meet_query = "SELECT * FROM meeting_minutes";
// We can filter meetings by year as well (Year + 543)
if (!empty($selected_year)) {
$meet_query .= " WHERE YEAR(meeting_date) + 543 = :year";
}
$meet_query .= " ORDER BY meeting_date DESC";
$meet_stmt = $pdo->prepare($meet_query);
if (!empty($selected_year)) {
$meet_stmt->bindParam(':year', $selected_year, PDO::PARAM_INT);
}
$meet_stmt->execute();
$meetings = $meet_stmt->fetchAll();
// Extract unique years from meeting dates
$years_stmt = $pdo->query("SELECT DISTINCT YEAR(meeting_date) + 543 as year_th FROM meeting_minutes ORDER BY year_th DESC");
$years = $years_stmt->fetchAll(PDO::FETCH_COLUMN);
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ระบบจัดเก็บผลงาน P4Q</title>
<link rel="icon" type="image/png" href="logo.png">
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
<style>
body {
font-family: 'Sarabun', sans-serif;
background-color: #f4f6f9;
}
.content-card {
background-color: #ffffff;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
border: 1px solid #e5e7eb;
}
/* Custom DataTable */
.dataTables_wrapper .dataTables_filter input {
border: 1px solid #ced4da;
border-radius: 0.25rem;
padding: 0.375rem 0.75rem;
outline: none;
}
.dataTables_wrapper .dataTables_filter input:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
}
table.dataTable thead th { border-bottom: 2px solid #dee2e6; }
table.dataTable.no-footer { border-bottom: 1px solid #dee2e6; }
/* Sidebar transition */
#sidebar {
transition: transform 0.3s ease, width 0.3s ease;
}
.sidebar-collapsed {
width: 4rem; /* w-16 */
}
.sidebar-collapsed .menu-text {
display: none;
}
.sidebar-collapsed .logo-text {
display: none;
}
@media (max-width: 768px) {
#sidebar {
position: absolute;
height: 100%;
z-index: 50;
transform: translateX(-100%);
}
#sidebar.mobile-open {
transform: translateX(0);
}
}
</style>
</head>
<body class="text-gray-800 h-screen overflow-hidden flex">
<!-- Sidebar -->
<aside id="sidebar" class="w-64 bg-green-900 text-white flex flex-col flex-shrink-0 shadow-lg">
<div class="h-16 flex items-center justify-center px-4 border-b border-green-800">
<img src="logo.png" alt="Logo" class="h-10 w-auto bg-white p-1 rounded-full" onerror="this.src='https://via.placeholder.com/40'">
<span class="logo-text ml-3 font-bold text-lg whitespace-nowrap overflow-hidden">P4Q ระบบผลงาน</span>
</div>
<nav class="flex-1 py-4 overflow-y-auto">
<a href="?page=competitions" class="flex items-center px-4 py-3 <?= $page === 'competitions' ? 'bg-green-800 border-l-4 border-white text-white' : 'text-green-100 hover:bg-green-800 hover:text-white' ?> transition-colors">
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path></svg>
<span class="menu-text ml-3 font-medium whitespace-nowrap">รายการประกวดผลงาน</span>
</a>
<a href="?page=meetings" class="flex items-center px-4 py-3 <?= $page === 'meetings' ? 'bg-green-800 border-l-4 border-white text-white' : 'text-green-100 hover:bg-green-800 hover:text-white' ?> transition-colors">
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"></path></svg>
<span class="menu-text ml-3 font-medium whitespace-nowrap">บันทึกประชุมคณะกรรมการ</span>
</a>
</nav>
<div class="p-4 border-t border-green-800">
<a href="admin/login.php" class="flex items-center text-green-200 hover:text-white transition-colors" title="ผู้ดูแลระบบ">
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
<span class="menu-text ml-3 text-sm">ผู้ดูแลระบบ</span>
</a>
</div>
</aside>
<!-- Overlay for mobile sidebar -->
<div id="sidebarOverlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
<!-- Main Content Wrapper -->
<div class="flex-1 flex flex-col h-screen overflow-hidden relative">
<!-- Header / Navbar -->
<header class="h-16 bg-white shadow-sm flex items-center justify-between px-4 sm:px-6 lg:px-8 flex-shrink-0 z-10 relative">
<div class="flex items-center">
<button id="sidebarToggle" class="text-gray-500 hover:text-green-700 focus:outline-none p-2 mr-2 rounded-md hover:bg-gray-100 transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
</button>
<h2 class="text-xl font-bold text-gray-800">
<?= $page === 'competitions' ? 'รายการผลงานที่ส่งประกวด' : 'บันทึกการประชุมคณะกรรมการ' ?>
</h2>
</div>
</header>
<!-- Scrollable Main Content -->
<main class="flex-1 overflow-y-auto p-4 md:p-8 relative">
<div class="max-w-7xl mx-auto">
<!-- Filter Section -->
<div class="content-card p-5 mb-6 flex flex-col sm:flex-row items-center justify-between border-l-4 border-l-blue-600">
<h3 class="font-bold text-gray-800 mb-4 sm:mb-0">ค้นหาข้อมูลตามปี</h3>
<form action="" method="GET" class="flex items-center space-x-3 w-full sm:w-auto">
<input type="hidden" name="page" value="<?= htmlspecialchars((string)$page) ?>">
<label for="year" class="text-sm font-semibold text-gray-700 whitespace-nowrap">เลือกปี (พ.ศ.):</label>
<select name="year" id="year" class="form-select block w-full sm:w-48 pl-3 pr-8 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded border bg-gray-50">
<option value="">-- แสดงทั้งหมด --</option>
<?php foreach ($years as $y): ?>
<option value="<?= $y ?>" <?= $selected_year == $y ? 'selected' : '' ?>><?= $y ?></option>
<?php endforeach; ?>
</select>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors shadow-sm whitespace-nowrap">
กรองข้อมูล
</button>
</form>
</div>
<?php if ($page === 'competitions'): ?>
<!-- Competitions Table -->
<div class="content-card p-6">
<div class="overflow-x-auto">
<table class="datatable w-full text-left border-collapse border border-gray-200">
<thead>
<tr class="bg-gray-100 text-gray-700 text-sm">
<th class="border py-3 px-4 font-bold text-center w-24">ปี (พ.ศ.)</th>
<th class="border py-3 px-4 font-bold">ชื่อผลงาน</th>
<th class="border py-3 px-4 font-bold text-center w-32">ผลรางวัล</th>
<th class="border py-3 px-4 font-bold">ผู้จัดทำ</th>
<th class="border py-3 px-4 font-bold text-center w-32">ไฟล์แนบ</th>
</tr>
</thead>
<tbody class="text-sm">
<?php foreach ($competitions as $c): ?>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border py-3 px-4 text-center font-medium text-gray-700"><?= $c['year_th'] ?></td>
<td class="border py-3 px-4">
<div class="font-bold text-blue-900"><?= htmlspecialchars((string)$c['title']) ?></div>
<?php if (!empty(trim(strip_tags((string)$c['details'])))): ?>
<button onclick="document.getElementById('details_comp_<?= $c['id'] ?>').classList.toggle('hidden')" class="text-xs text-blue-600 hover:text-blue-800 hover:underline mt-1 focus:outline-none flex items-center">
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
ดูรายละเอียด
</button>
<div id="details_comp_<?= $c['id'] ?>" class="hidden mt-2 p-3 bg-blue-50 border border-blue-100 rounded text-sm text-gray-700">
<?= $c['details'] ?>
</div>
<?php endif; ?>
</td>
<td class="border py-3 px-4 text-center">
<span class="inline-flex items-center justify-center px-2.5 py-1 rounded text-xs font-bold bg-yellow-50 text-yellow-700 border border-yellow-400">
<?= htmlspecialchars((string)$c['rank_result']) ?>
</span>
</td>
<td class="border py-3 px-4 text-gray-600"><?= htmlspecialchars((string)$c['authors']) ?></td>
<td class="border py-3 px-4 align-middle">
<?php
$att_stmt = $pdo->prepare("SELECT * FROM competition_attachments WHERE competition_id = ?");
$att_stmt->execute([$c['id']]);
$files = $att_stmt->fetchAll();
if (count($files) > 0): ?>
<div class="flex flex-col space-y-1 items-center">
<?php foreach ($files as $file): ?>
<a href="https://drive.google.com/file/d/<?= htmlspecialchars((string)$file['gdrive_file_id']) ?>/view" target="_blank" class="inline-flex items-center justify-center text-xs text-white bg-red-600 hover:bg-red-700 border border-red-700 px-2 py-1 rounded shadow-sm transition-colors w-full" title="<?= htmlspecialchars((string)$file['file_name']) ?>">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"></path></svg>
PDF
</a>
<?php endforeach; ?>
</div>
<?php else: ?>
<span class="text-xs text-gray-400 block text-center">-</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($page === 'meetings'): ?>
<!-- Meetings Table -->
<div class="content-card p-6">
<div class="overflow-x-auto">
<table class="datatable w-full text-left border-collapse border border-gray-200">
<thead>
<tr class="bg-gray-100 text-gray-700 text-sm">
<th class="border py-3 px-4 font-bold w-32 text-center">วันที่ประชุม</th>
<th class="border py-3 px-4 font-bold">เรื่อง</th>
<th class="border py-3 px-4 font-bold text-center w-32">ไฟล์แนบ</th>
</tr>
</thead>
<tbody class="text-sm">
<?php foreach ($meetings as $m): ?>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border py-3 px-4 text-gray-700 font-medium text-center whitespace-nowrap"><?= date('d/m/Y', strtotime($m['meeting_date'])) ?></td>
<td class="border py-3 px-4">
<div class="font-bold text-blue-900"><?= htmlspecialchars((string)$m['title']) ?></div>
<?php if (!empty(trim(strip_tags((string)$m['details'])))): ?>
<button onclick="document.getElementById('details_meet_<?= $m['id'] ?>').classList.toggle('hidden')" class="text-xs text-blue-600 hover:text-blue-800 hover:underline mt-1 focus:outline-none flex items-center">
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
ดูรายละเอียด
</button>
<div id="details_meet_<?= $m['id'] ?>" class="hidden mt-2 p-3 bg-blue-50 border border-blue-100 rounded text-sm text-gray-700">
<?= $m['details'] ?>
</div>
<?php endif; ?>
</td>
<td class="border py-3 px-4 align-middle">
<?php
$att_stmt = $pdo->prepare("SELECT * FROM meeting_attachments WHERE meeting_id = ?");
$att_stmt->execute([$m['id']]);
$files = $att_stmt->fetchAll();
if (count($files) > 0): ?>
<div class="flex flex-col space-y-1 items-center">
<?php foreach ($files as $file): ?>
<a href="https://drive.google.com/file/d/<?= htmlspecialchars((string)$file['gdrive_file_id']) ?>/view" target="_blank" class="inline-flex items-center justify-center text-xs text-white bg-red-600 hover:bg-red-700 border border-red-700 px-2 py-1 rounded shadow-sm transition-colors w-full" title="<?= htmlspecialchars((string)$file['file_name']) ?>">
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"></path></svg>
ดาวน์โหลด
</a>
<?php endforeach; ?>
</div>
<?php else: ?>
<span class="text-xs text-gray-400 block text-center">-</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
</main>
<!-- Fixed Footer -->
<footer class="bg-white border-t border-gray-200 py-4 text-center text-sm text-gray-500 w-full flex-shrink-0 z-10 shadow-[0_-2px_10px_rgba(0,0,0,0.05)]">
&copy; <?= date('Y') ?> ระบบจัดเก็บผลงาน P4Q. โรงพยาบาล
</footer>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
$('.datatable').DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.6/i18n/th.json"
},
"pageLength": 10,
"order": [] // disable initial sort to keep SQL ordering
});
// Sidebar Toggle Logic
const sidebar = document.getElementById('sidebar');
const sidebarToggle = document.getElementById('sidebarToggle');
const sidebarOverlay = document.getElementById('sidebarOverlay');
sidebarToggle.addEventListener('click', function() {
if (window.innerWidth < 768) {
// Mobile: slide in/out
sidebar.classList.toggle('mobile-open');
sidebarOverlay.classList.toggle('hidden');
} else {
// Desktop: collapse to icons
sidebar.classList.toggle('sidebar-collapsed');
}
});
// Close sidebar when clicking overlay on mobile
sidebarOverlay.addEventListener('click', function() {
sidebar.classList.remove('mobile-open');
sidebarOverlay.classList.add('hidden');
});
// Handle window resize to reset states if needed
window.addEventListener('resize', function() {
if (window.innerWidth >= 768) {
sidebar.classList.remove('mobile-open');
sidebarOverlay.classList.add('hidden');
} else {
sidebar.classList.remove('sidebar-collapsed');
}
});
});
</script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB