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
@@ -0,0 +1,66 @@
<?php
header('Content-Type: application/json; charset=utf-8');
$year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
$holidays = [];
function getThaiHolidayName($title) {
$title = strtolower($title);
if (strpos($title, "new year's day") !== false) return "วันขึ้นปีใหม่";
if (strpos($title, "makha bucha") !== false) return "วันมาฆบูชา";
if (strpos($title, "chakri") !== false) return "วันจักรี";
if (strpos($title, "songkran") !== false) return "เทศกาลสงกรานต์";
if (strpos($title, "labour") !== false || strpos($title, "labor") !== false) return "วันแรงงานแห่งชาติ";
if (strpos($title, "coronation") !== false) return "วันฉัตรมงคล";
if (strpos($title, "visakha bucha") !== false) return "วันวิสาขบูชา";
if (strpos($title, "queen suthida") !== false) return "วันเฉลิมพระชนมพรรษา สมเด็จพระนางเจ้าฯ พระบรมราชินี";
if (strpos($title, "maha vajiralongkorn") !== false) return "วันเฉลิมพระชนมพรรษา พระบาทสมเด็จพระเจ้าอยู่หัว";
if (strpos($title, "asanha bucha") !== false) return "วันอาสาฬหบูชา";
if (strpos($title, "buddhist lent") !== false || strpos($title, "khao phansa") !== false) return "วันเข้าพรรษา";
if (strpos($title, "queen sirikit") !== false || strpos($title, "mother's day") !== false) return "วันเฉลิมพระชนมพรรษา สมเด็จพระบรมราชชนนีพันปีหลวง และวันแม่แห่งชาติ";
if (strpos($title, "bhumibol adulyadej the great memorial") !== false || strpos($title, "navamindra") !== false) return "วันนวมินทรมหาราช";
if (strpos($title, "chulalongkorn") !== false) return "วันปิยมหาราช";
if (strpos($title, "bhumibol adulyadej's birthday") !== false || strpos($title, "father's day") !== false) return "วันคล้ายวันพระบรมราชสมภพ ร.9 และวันพ่อแห่งชาติ";
if (strpos($title, "constitution") !== false) return "วันรัฐธรรมนูญ";
if (strpos($title, "new year's eve") !== false) return "วันสิ้นปี";
if (strpos($title, "royal ploughing") !== false) return "วันพืชมงคล";
if (strpos($title, "special") !== false) return "วันหยุดพิเศษ";
return $title; // fallback
}
try {
$apiUrl = "https://thailandformats.com/api/v1/holidays/" . $year;
$ctx = stream_context_create(array('http' => array('timeout' => 5)));
$holidayJson = @file_get_contents($apiUrl, false, $ctx);
if ($holidayJson) {
$holidayData = json_decode($holidayJson, true);
if (isset($holidayData['holidays'])) {
foreach ($holidayData['holidays'] as $h) {
$startDate = new DateTime($h['start_date']);
$endDate = new DateTime($h['end_date']);
$titleEn = $h['title'];
$isSub = (stripos($titleEn, 'substitution') !== false || stripos($titleEn, 'observed') !== false);
$name = getThaiHolidayName($titleEn);
if ($isSub) {
$name = 'ชดเชย' . str_replace('ชดเชย', '', $name);
}
for ($d = $startDate; $d <= $endDate; $d->modify('+1 day')) {
$dateStr = $d->format('Y-m-d');
$holidays[$dateStr] = $name;
}
}
}
}
echo json_encode(['status' => 'success', 'data' => $holidays]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>
@@ -0,0 +1,51 @@
<?php
require_once '../config.php';
header('Content-Type: application/json; charset=utf-8');
try {
// Auto-create table
$pdo->exec("CREATE TABLE IF NOT EXISTS `monthly_locks` (
`year` int(4) NOT NULL,
`month` int(2) NOT NULL,
`is_locked` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`year`,`month`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET') {
$year = isset($_GET['year']) ? intval($_GET['year']) : date('Y');
$month = isset($_GET['month']) ? intval($_GET['month']) : date('n');
$stmt = $pdo->prepare("SELECT is_locked FROM monthly_locks WHERE year = ? AND month = ?");
$stmt->execute([$year, $month]);
$row = $stmt->fetch();
echo json_encode([
'status' => 'success',
'is_locked' => $row ? (bool)$row['is_locked'] : false
]);
exit;
}
if ($method === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$year = intval($data['year']);
$month = intval($data['month']);
$is_locked = empty($data['is_locked']) ? 0 : 1;
$stmt = $pdo->prepare("INSERT INTO monthly_locks (year, month, is_locked) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE is_locked = VALUES(is_locked)");
$stmt->execute([$year, $month, $is_locked]);
echo json_encode([
'status' => 'success',
'is_locked' => (bool)$is_locked
]);
exit;
}
} catch (Exception $e) {
echo json_encode([
'status' => 'error',
'message' => $e->getMessage()
]);
}
@@ -0,0 +1,140 @@
<?php
require_once __DIR__ . '/../config.php';
// Telegram Bot Configuration - Please change these
define('TELEGRAM_BOT_TOKEN', 'YOUR_TELEGRAM_BOT_TOKEN_HERE');
define('TELEGRAM_CHAT_ID', 'YOUR_TELEGRAM_CHAT_ID_HERE');
// Optionally get date from query param, default to today
$dateStr = isset($_GET['date']) ? $_GET['date'] : date('Y-m-d');
$date = new DateTime($dateStr);
$year = (int)$date->format('Y');
$month = (int)$date->format('n');
$day = (int)$date->format('j');
// Thai Date formatting
$thaiMonths = [
1 => 'มกราคม', 2 => 'กุมภาพันธ์', 3 => 'มีนาคม', 4 => 'เมษายน',
5 => 'พฤษภาคม', 6 => 'มิถุนายน', 7 => 'กรกฎาคม', 8 => 'สิงหาคม',
9 => 'กันยายน', 10 => 'ตุลาคม', 11 => 'พฤศจิกายน', 12 => 'ธันวาคม'
];
$thaiDays = [
'Sunday' => 'อาทิตย์', 'Monday' => 'จันทร์', 'Tuesday' => 'อังคาร',
'Wednesday' => 'พุธ', 'Thursday' => 'พฤหัสบดี', 'Friday' => 'ศุกร์', 'Saturday' => 'เสาร์'
];
$thaiDateStr = "วัน" . $thaiDays[$date->format('l')] . "ที่ " . $day . " " . $thaiMonths[$month] . " " . ($year + 543);
try {
// Get schedules for the target date
$stmt = $pdo->prepare("
SELECT s.shift_type, st.name, st.phone
FROM schedules s
JOIN staff st ON s.staff_id = st.id
WHERE s.year = :year AND s.month = :month AND s.day = :day
");
$stmt->execute([':year' => $year, ':month' => $month, ':day' => $day]);
$schedules = $stmt->fetchAll();
if (empty($schedules)) {
// No schedule for today, maybe send nothing or send a specific message
$message = "\n📅 <b>ประกาศตารางเวร</b>\n$thaiDateStr\n\n✨ <i>วันนี้ไม่มีผู้เข้าเวรพักผ่อนให้เต็มที่ครับ</i> ✨";
} else {
$morning = [];
$afternoon = [];
$both = [];
$leave = [];
foreach ($schedules as $row) {
$nameInfo = $row['name'];
if (!empty($row['phone'])) {
$nameInfo .= " (📞 " . $row['phone'] . ")";
}
if ($row['shift_type'] === 'ช') {
$morning[] = $nameInfo;
} elseif ($row['shift_type'] === 'บ') {
$afternoon[] = $nameInfo;
} elseif ($row['shift_type'] === 'ช/บ') {
$both[] = $nameInfo;
} elseif ($row['shift_type'] === 'ลา') {
$leave[] = $nameInfo;
}
}
// Build beautiful message using HTML tags for Telegram
$message = "📢 <b>แจ้งเตือนตารางเวรประจำวัน</b> 📢\n";
$message .= "🗓️ <b>$thaiDateStr</b>\n";
$message .= str_repeat("", 12) . "\n\n";
if (!empty($morning) || !empty($both)) {
$message .= "🌅 <b>เวรเช้า (08:30 - 16:30 น.)</b>\n";
foreach ($both as $name) {
$message .= " 🔹 " . $name . "\n";
}
foreach ($morning as $name) {
$message .= " 🔹 " . $name . "\n";
}
$message .= "\n";
}
if (!empty($afternoon) || !empty($both)) {
$message .= "🌇 <b>เวรบ่าย (16:30 - 00:30 น.)</b>\n";
foreach ($both as $name) {
$message .= " 🔸 " . $name . "\n";
}
foreach ($afternoon as $name) {
$message .= " 🔸 " . $name . "\n";
}
$message .= "\n";
}
if (!empty($leave)) {
$message .= "⛔ <b>ลาพัก/ลากิจ</b>\n";
foreach ($leave as $name) {
$message .= "" . $name . "\n";
}
$message .= "\n";
}
$message .= "💡 <i>ศูนย์คอมพิวเตอร์ ฝ่ายคอมพิวเตอร์และสารสนเทศ</i>";
}
// Only send to Telegram if token is set and not default
if (TELEGRAM_BOT_TOKEN !== 'YOUR_TELEGRAM_BOT_TOKEN_HERE' && !empty(TELEGRAM_BOT_TOKEN)) {
$url = "https://api.telegram.org/bot" . TELEGRAM_BOT_TOKEN . "/sendMessage";
$postData = [
'chat_id' => TELEGRAM_CHAT_ID,
'text' => $message,
'parse_mode' => 'HTML'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($ch);
curl_close($ch);
echo json_encode([
'status' => 'success',
'message' => 'Notification sent successfully',
'response' => json_decode($result),
'text' => $message // For preview
]);
} else {
echo json_encode([
'status' => 'success',
'message' => 'Preview mode (No Telegram Token)',
'text' => $message // For preview
]);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>
@@ -0,0 +1,69 @@
<?php
require_once '../config.php';
$method = $_SERVER['REQUEST_METHOD'];
// Ensure table exists
try {
$pdo->exec("
CREATE TABLE IF NOT EXISTS `oncall_days` (
`day_of_week` int(11) NOT NULL,
`staff_id` int(11) NOT NULL,
PRIMARY KEY (`day_of_week`),
CONSTRAINT `fk_oncall_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
} catch (PDOException $e) {}
switch ($method) {
case 'GET':
try {
$stmt = $pdo->query("SELECT day_of_week, staff_id FROM oncall_days");
$data = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); // returns [day_of_week => staff_id]
// If empty, return defaults 1->1, 2->2...
if (empty($data)) {
$data = [1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5];
}
echo json_encode(['status' => 'success', 'data' => $data]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
break;
case 'POST':
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['day_of_week']) || !isset($input['staff_id'])) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Missing required fields']);
exit;
}
try {
$stmt = $pdo->prepare("
INSERT INTO oncall_days (day_of_week, staff_id)
VALUES (:day_of_week, :staff_id)
ON DUPLICATE KEY UPDATE staff_id = :staff_id_update
");
$stmt->execute([
':day_of_week' => $input['day_of_week'],
':staff_id' => $input['staff_id'],
':staff_id_update' => $input['staff_id']
]);
echo json_encode(['status' => 'success', 'message' => 'On-call updated successfully']);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
break;
default:
http_response_code(405);
echo json_encode(['status' => 'error', 'message' => 'Method not allowed']);
break;
}
?>
@@ -0,0 +1,130 @@
<?php
require_once '../config.php';
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
// Get schedules for a specific month and year
$year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
$month = isset($_GET['month']) ? (int)$_GET['month'] : date('n');
try {
$stmt = $pdo->prepare("SELECT staff_id, day, shift_type FROM schedules WHERE year = :year AND month = :month");
$stmt->execute([':year' => $year, ':month' => $month]);
$schedules = $stmt->fetchAll();
// Format data for frontend: { staffId: { day: 'shift_type' } }
$formattedData = [];
foreach ($schedules as $row) {
$staffId = $row['staff_id'];
$day = $row['day'];
$shiftType = $row['shift_type'];
if (!isset($formattedData[$staffId])) {
$formattedData[$staffId] = [];
}
$formattedData[$staffId][$day] = $shiftType;
}
echo json_encode(['status' => 'success', 'data' => $formattedData]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
break;
case 'POST':
// Save or update a shift
$input = json_decode(file_get_contents('php://input'), true);
// Check if this is a bulk operation
if (isset($input['bulk']) && is_array($input['schedules'])) {
try {
$pdo->beginTransaction();
// Optional: clear existing month data if requested
if (isset($input['clear_month']) && $input['clear_month']) {
$stmt = $pdo->prepare("DELETE FROM schedules WHERE year = :year AND month = :month");
$stmt->execute([
':year' => $input['year'],
':month' => $input['month']
]);
}
$insertStmt = $pdo->prepare("INSERT INTO schedules (staff_id, year, month, day, shift_type) VALUES (:staff_id, :year, :month, :day, :shift_type) ON DUPLICATE KEY UPDATE shift_type = :shift_type");
foreach ($input['schedules'] as $schedule) {
if ($schedule['shift_type'] !== '') {
$insertStmt->execute([
':staff_id' => $schedule['staff_id'],
':year' => $input['year'],
':month' => $input['month'],
':day' => $schedule['day'],
':shift_type' => $schedule['shift_type']
]);
}
}
$pdo->commit();
echo json_encode(['status' => 'success', 'message' => 'Bulk schedules saved']);
} catch (PDOException $e) {
$pdo->rollBack();
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
exit;
}
if (!isset($input['staff_id']) || !isset($input['year']) || !isset($input['month']) || !isset($input['day'])) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Missing required fields']);
exit;
}
$staffId = $input['staff_id'];
$year = $input['year'];
$month = $input['month'];
$day = $input['day'];
$shiftType = isset($input['shift_type']) ? $input['shift_type'] : '';
try {
if ($shiftType === '') {
// If shift_type is empty, delete the record
$stmt = $pdo->prepare("DELETE FROM schedules WHERE staff_id = :staff_id AND year = :year AND month = :month AND day = :day");
$stmt->execute([
':staff_id' => $staffId,
':year' => $year,
':month' => $month,
':day' => $day
]);
} else {
// Insert or update (upsert)
$stmt = $pdo->prepare("
INSERT INTO schedules (staff_id, year, month, day, shift_type)
VALUES (:staff_id, :year, :month, :day, :shift_type)
ON DUPLICATE KEY UPDATE shift_type = :shift_type_update
");
$stmt->execute([
':staff_id' => $staffId,
':year' => $year,
':month' => $month,
':day' => $day,
':shift_type' => $shiftType,
':shift_type_update' => $shiftType
]);
}
echo json_encode(['status' => 'success', 'message' => 'Schedule updated successfully']);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
break;
default:
http_response_code(405);
echo json_encode(['status' => 'error', 'message' => 'Method not allowed']);
break;
}
?>
@@ -0,0 +1,101 @@
<?php
require_once '../config.php';
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
// Get all staff
try {
// Also try to add phone and preferences columns if they don't exist
try { $pdo->exec("ALTER TABLE staff ADD COLUMN phone VARCHAR(20) DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE staff ADD COLUMN pref_weekdays VARCHAR(50) DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE staff ADD COLUMN pref_weekend VARCHAR(10) DEFAULT NULL"); } catch (Exception $e) {}
$stmt = $pdo->query("SELECT id, name, phone, pref_weekdays, pref_weekend FROM staff ORDER BY id ASC");
$staff = $stmt->fetchAll();
echo json_encode(['status' => 'success', 'data' => $staff]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
break;
case 'PUT':
case 'POST':
// Add or Update staff name, phone, and preferences
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['name'])) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Missing name']);
exit;
}
try {
$phone = isset($input['phone']) ? $input['phone'] : null;
$pref_weekdays = isset($input['pref_weekdays']) ? $input['pref_weekdays'] : null;
$pref_weekend = isset($input['pref_weekend']) ? $input['pref_weekend'] : null;
if (isset($input['id']) && $input['id']) {
// Update existing
$stmt = $pdo->prepare("UPDATE staff SET name = :name, phone = :phone, pref_weekdays = :pref_weekdays, pref_weekend = :pref_weekend WHERE id = :id");
$stmt->execute([
':name' => $input['name'],
':phone' => $phone,
':pref_weekdays' => $pref_weekdays,
':pref_weekend' => $pref_weekend,
':id' => $input['id']
]);
echo json_encode(['status' => 'success', 'message' => 'Staff updated successfully']);
} else {
// Insert new
$stmt = $pdo->prepare("INSERT INTO staff (name, phone, pref_weekdays, pref_weekend) VALUES (:name, :phone, :pref_weekdays, :pref_weekend)");
$stmt->execute([
':name' => $input['name'],
':phone' => $phone,
':pref_weekdays' => $pref_weekdays,
':pref_weekend' => $pref_weekend
]);
$newId = $pdo->lastInsertId();
echo json_encode(['status' => 'success', 'message' => 'Staff added successfully', 'id' => $newId]);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
break;
case 'DELETE':
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['id'])) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Missing id']);
exit;
}
try {
// Delete staff
$stmt = $pdo->prepare("DELETE FROM staff WHERE id = :id");
$stmt->execute([':id' => $input['id']]);
// Delete related schedules
$stmt = $pdo->prepare("DELETE FROM schedules WHERE staff_id = :id");
$stmt->execute([':id' => $input['id']]);
// Delete related oncall
$stmt = $pdo->prepare("DELETE FROM oncall_days WHERE staff_id = :id");
$stmt->execute([':id' => $input['id']]);
echo json_encode(['status' => 'success', 'message' => 'Staff deleted successfully']);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
break;
default:
http_response_code(405);
echo json_encode(['status' => 'error', 'message' => 'Method not allowed']);
break;
}
?>
@@ -0,0 +1,25 @@
<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '@Samui@10742'); // Change this based on your local setup (e.g., empty string for XAMPP default)
define('DB_NAME', 'it_ot');
// App Password
define('APP_PASSWORD', 'ijawa');
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS);
// Set PDO error mode to exception
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Set default fetch mode to associative array
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die(json_encode([
'status' => 'error',
'message' => 'Connection failed: ' . $e->getMessage()
]));
}
// Ensure the response is JSON
header('Content-Type: application/json; charset=utf-8');
@@ -0,0 +1,32 @@
-- Table structure for table `staff`
CREATE TABLE IF NOT EXISTS `staff` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`phone` varchar(20) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Insert initial staff data
INSERT INTO `staff` (`id`, `name`, `phone`) VALUES
(1, 'นางจิตติมา', '08X-XXX-4451'),
(2, 'นายพงษ์ศักดิ์', '08X-XXX-5514'),
(3, 'นางสาวกานต์', '08X-XXX-9616'),
(4, 'นายประวิทย์', '08X-XXX-0935'),
(5, 'นางพรพิมล', '08X-XXX-9731')
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`), `phone`=VALUES(`phone`);
-- Table structure for table `schedules`
CREATE TABLE IF NOT EXISTS `schedules` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`staff_id` int(11) NOT NULL,
`year` int(4) NOT NULL,
`month` int(2) NOT NULL,
`day` int(2) NOT NULL,
`shift_type` varchar(10) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_schedule` (`staff_id`,`year`,`month`,`day`),
CONSTRAINT `fk_schedules_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,299 @@
<?php
session_start();
require_once 'config.php';
header('Content-Type: text/html; charset=utf-8');
if (empty($_SESSION['logged_in'])) {
header("Location: index.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>เอกสารการจัดเวร</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Sarabun', sans-serif;
background-color: #f1f5f9;
}
@media print {
@page {
size: A4;
margin: 5mm;
}
body, main {
background-color: white;
overflow: visible !important;
height: auto !important;
}
.no-print {
display: none !important;
}
.print-area {
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
box-shadow: none !important;
overflow: visible !important;
}
.a4-page {
width: 100% !important;
margin: 0 !important;
padding: 5mm 10mm !important;
box-shadow: none !important;
min-height: auto !important;
}
.doc-table th, .doc-table td {
padding: 2px 4px !important;
}
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
}
.a4-page {
width: 210mm;
min-height: 297mm;
background: white;
margin: 0 auto;
padding: 20mm;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.doc-table th, .doc-table td {
border: 1px solid #000;
padding: 2px 4px;
font-size: 12px;
line-height: 1.2;
}
.doc-table th {
text-align: center;
font-weight: bold;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
</style>
</head>
<body class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<aside class="w-64 bg-slate-800 text-white flex flex-col no-print">
<div class="p-4 bg-slate-900 font-bold text-lg flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
ระบบพิมพ์เอกสาร
</div>
<nav class="flex-1 overflow-y-auto py-4">
<ul class="space-y-1">
<li>
<a href="#" class="block px-4 py-2 bg-blue-600 text-white font-medium border-l-4 border-white">
บัญชีลงชื่อการปฏิบัติงาน
</a>
</li>
<!-- Future documents will be added here -->
</ul>
</nav>
<div class="p-4 border-t border-slate-700">
<button onclick="window.close()" class="w-full py-2 bg-slate-700 hover:bg-slate-600 rounded text-center text-sm transition-colors">
ปิดหน้าต่าง
</button>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 overflow-y-auto flex flex-col">
<!-- Topbar -->
<header class="bg-white shadow-sm p-4 flex justify-between items-center no-print">
<h1 class="text-xl font-bold text-slate-800" id="docTitle">บัญชีลงชื่อการปฏิบัติงาน</h1>
<div class="flex gap-2">
<button onclick="window.print()" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded shadow flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
พิมพ์เอกสาร
</button>
</div>
</header>
<!-- Document Area -->
<div class="p-8 print-area flex-1 overflow-y-auto">
<div class="a4-page text-black" id="documentContent">
<!-- Data will be populated here -->
<div class="text-center mb-2 leading-tight">
<h2 class="font-bold text-[14px]">บัญชีลงชื่อการปฏิบัติงาน กลุ่มงาน/สุขภาพดิจิทัล</h2>
<h3 class="font-bold text-[14px]">ประจำวันที่ <span id="dateRangeDisplay"></span> เดือน <span id="monthDisplay"></span> พ.ศ. <span id="yearDisplay"></span></h3>
</div>
<table class="w-full doc-table border-collapse mb-8">
<thead>
<tr class="whitespace-nowrap bg-gray-100">
<th class="w-24">ว.ด.ป.</th>
<th class="w-64">ชื่อ - สกุล(ตัวบรรจง)</th>
<th class="w-24">ลายมือชื่อ</th>
<th class="w-20">เวลามา</th>
<th class="w-24">ลายมือชื่อ</th>
<th class="w-20">เวลากลับ</th>
<th>หมายเหตุ</th>
</tr>
</thead>
<tbody id="tableBody">
<tr><td colspan="7" class="text-center py-4">กำลังโหลดข้อมูล...</td></tr>
</tbody>
</table>
<div class="flex justify-end mt-6 pr-12 text-[12px]">
<div class="text-center">
<div class="mb-2">ลงชื่อ .................................................... ผู้ควบคุมกำกับ</div>
<div contenteditable="true" class="hover:bg-gray-100 px-2 py-1 rounded inline-block outline-none min-w-[200px] text-center">(นางจิตติมา ทองนาค)</div>
<br>
<div contenteditable="true" class="hover:bg-gray-100 px-2 py-1 rounded inline-block outline-none min-w-[200px] text-center">นักวิชาการคอมพิวเตอร์ชำนาญการ</div>
</div>
</div>
</div>
</div>
</main>
<script>
const urlParams = new URLSearchParams(window.location.search);
const qYear = parseInt(urlParams.get('year')) || new Date().getFullYear();
const urlMonth = parseInt(urlParams.get('month'));
const qMonth = !isNaN(urlMonth) ? urlMonth - 1 : new Date().getMonth();
const thaiMonths = [
"มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน",
"กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม"
];
const thaiMonthsShort = [
"ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.",
"ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."
];
document.getElementById('monthDisplay').textContent = thaiMonths[qMonth];
document.getElementById('yearDisplay').textContent = qYear + 543;
let staffList = [];
let scheduleData = {};
function getDaysInMonth(month, year) {
return new Date(year, month + 1, 0).getDate();
}
async function init() {
const daysInMonth = getDaysInMonth(qMonth, qYear);
document.getElementById('dateRangeDisplay').textContent = `1-${daysInMonth}`;
try {
// Fetch Staff
const staffRes = await fetch('api/staff.php');
const staffJson = await staffRes.json();
staffList = staffJson.data || [];
// Fetch Schedules (Database uses 1-12 for month)
const apiMonth = qMonth + 1;
const schedRes = await fetch(`api/schedules.php?year=${qYear}&month=${apiMonth}`);
const schedJson = await schedRes.json();
// Process schedule structure: scheduleData[day] = [staffIds]
const dailySchedules = {};
for (let d = 1; d <= daysInMonth; d++) {
dailySchedules[d] = [];
}
if (schedJson.data) {
for (const staffId in schedJson.data) {
for (const dayStr in schedJson.data[staffId]) {
const shift = schedJson.data[staffId][dayStr];
if (shift === 'ช' || shift === 'บ' || shift === 'ช/บ' || shift === 'ป') {
const day = parseInt(dayStr);
if (dailySchedules[day]) {
dailySchedules[day].push({ staffId: staffId, shift: shift });
}
}
}
}
}
renderTable(dailySchedules, daysInMonth);
} catch (err) {
console.error(err);
document.getElementById('tableBody').innerHTML = '<tr><td colspan="7" class="text-center text-red-600 py-4">เกิดข้อผิดพลาดในการโหลดข้อมูล</td></tr>';
}
}
function getStaffName(id) {
const staff = staffList.find(s => s.id == id);
return staff ? staff.name : 'ไม่ทราบชื่อ';
}
function renderTable(dailySchedules, daysInMonth) {
const tbody = document.getElementById('tableBody');
let html = '';
const yearShort = (qYear + 543).toString().substring(2);
let hasData = false;
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${day.toString().padStart(2, '0')}-${thaiMonthsShort[qMonth]}-${yearShort}`;
const staffListOnDay = dailySchedules[day] || [];
if (staffListOnDay.length > 0) {
hasData = true;
staffListOnDay.forEach((record, index) => {
const staffName = getStaffName(record.staffId);
let timeIn = '';
let timeOut = '';
let note = '';
if (record.shift === 'ช') {
timeIn = '08.00';
timeOut = '16.00';
} else if (record.shift === 'บ') {
timeIn = '16.00';
timeOut = '24.00';
} else if (record.shift === 'ช/บ') {
timeIn = '08.00';
timeOut = '24.00';
} else if (record.shift === 'ป') {
timeIn = '08.00';
timeOut = '16.00';
note = 'ประชุม';
}
html += `
<tr class="whitespace-nowrap">
<td class="text-center">${dateStr}</td>
<td>
<div contenteditable="true" class="font-medium outline-none hover:bg-slate-100 rounded transition-colors">${staffName}</div>
</td>
<td></td>
<td class="text-center"><div contenteditable="true" class="outline-none hover:bg-slate-100 rounded">${timeIn}</div></td>
<td></td>
<td class="text-center"><div contenteditable="true" class="outline-none hover:bg-slate-100 rounded">${timeOut}</div></td>
<td><div contenteditable="true" class="outline-none hover:bg-slate-100 rounded w-full h-full min-h-[16px]">${note}</div></td>
</tr>
`;
});
}
}
if (!hasData) {
html = '<tr><td colspan="7" class="text-center py-8 text-slate-500">ไม่มีข้อมูลการปฏิบัติงานในเดือนนี้</td></tr>';
}
tbody.innerHTML = html;
}
init();
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB