<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#6366f1">
<meta name="description" content="ระบบจัดการนัดหมายแบบ PWA ที่ทันสมัย รองรับการทำงานทั้งออนไลน์และออฟไลน์">
<title>ระบบจัดการนัดหมาย</title>
<!-- PWA Manifest -->
<link rel="manifest" href="manifest.json">
<!-- Icons -->
<link rel="icon" href="icon-192.png" type="image/png">
<link rel="apple-touch-icon" href="icon-192.png">
<!-- Stylesheets -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Prompt:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Prompt', 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-attachment: fixed;
}
.container {
width: 95%;
max-width: 500px;
height: 90vh;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 32px 64px rgba(0, 0, 0, 0.15), 0 16px 32px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
color: white;
padding: 24px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
pointer-events: none;
}
.header h1 {
font-size: 20px;
font-weight: 600;
}
.status {
display: flex;
gap: 10px;
align-items: center;
}
.status i {
color: #10b981;
margin-right: 8px;
font-size: 14px;
animation: pulse 2s infinite;
}
.status i.offline {
color: #ef4444;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.chat-area {
flex: 1;
overflow-y: auto;
padding: 20px;
background: linear-gradient(to bottom, #f8fafc 0%, #f1f5f9 100%);
scroll-behavior: smooth;
}
.chat-area::-webkit-scrollbar {
width: 6px;
}
.chat-area::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 3px;
}
.chat-area::-webkit-scrollbar-thumb {
background: rgba(99, 102, 241, 0.3);
border-radius: 3px;
}
.chat-area::-webkit-scrollbar-thumb:hover {
background: rgba(99, 102, 241, 0.5);
}
.message {
margin-bottom: 15px;
display: flex;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.user {
justify-content: flex-end;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
word-wrap: break-word;
}
.message.bot .message-content {
background: white;
color: #1f2937;
border-bottom-left-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.message.user .message-content {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
color: white;
border-bottom-right-radius: 4px;
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
}
.appointment-card {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(139, 92, 246, 0.05) 100%);
border: 1px solid rgba(99, 102, 241, 0.2);
border-left: 4px solid #6366f1;
padding: 16px;
margin-top: 12px;
border-radius: 16px;
font-size: 14px;
backdrop-filter: blur(10px);
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.1);
transition: all 0.3s ease;
}
.appointment-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.2);
}
.appointment-card .title {
font-weight: 700;
color: #6366f1;
margin-bottom: 8px;
font-size: 15px;
}
.appointment-card .detail {
color: #64748b;
margin: 6px 0;
display: flex;
align-items: center;
gap: 8px;
}
.appointment-card .detail i {
color: #6366f1;
width: 16px;
text-align: center;
}
.input-area {
padding: 15px;
background: white;
border-top: 1px solid #e5e7eb;
display: flex;
gap: 10px;
}
.input-group {
flex: 1;
display: flex;
gap: 5px;
}
#messageInput {
flex: 1;
border: 2px solid rgba(99, 102, 241, 0.2);
border-radius: 25px;
padding: 12px 20px;
font-size: 15px;
outline: none;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
#messageInput:focus {
border-color: #6366f1;
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.2);
background: rgba(255, 255, 255, 1);
}
button {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
color: white;
border: none;
border-radius: 50%;
width: 45px;
height: 45px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-size: 16px;
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
}
button:hover {
transform: scale(1.1) translateY(-2px);
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.6);
}
button:active {
transform: scale(0.95);
}
button.recording {
background: #ef4444;
animation: recordPulse 1s infinite;
}
@keyframes recordPulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.quick-actions {
padding: 10px 20px;
background: #f9fafb;
display: flex;
gap: 8px;
overflow-x: auto;
border-top: 1px solid #e5e7eb;
}
.quick-btn {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border: 2px solid rgba(99, 102, 241, 0.2);
border-radius: 24px;
padding: 10px 18px;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
cursor: pointer;
transition: all 0.3s ease;
color: #4b5563;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.quick-btn:hover {
border-color: #6366f1;
color: #6366f1;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.2);
}
.notification {
position: fixed;
top: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 16px 24px;
border-radius: 16px;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.15);
display: none;
animation: slideInRight 0.3s ease;
z-index: 1000;
color: #374151;
font-weight: 500;
}
@keyframes slideInRight {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.notification.show {
display: block;
}
.typing-indicator {
display: none;
padding: 12px;
background: white;
border-radius: 18px;
width: 60px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.typing-indicator.show {
display: block;
}
.typing-indicator span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #6366f1;
margin: 0 2px;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%,
60%,
100% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
}
.pulse {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-calendar-check"></i> ผู้จัดการนัดหมาย</h1>
<div class="status">
<i class="fas fa-wifi" id="statusIcon"></i>
<span id="statusText">Online</span>
</div>
</div>
<div class="quick-actions">
<button class="quick-btn" onclick="quickAction('นัดหมายวันนี้')" title="วันนี้"><i class="fas fa-calendar-day"></i></button>
<button class="quick-btn" onclick="quickAction('นัดหมายพรุ่งนี้')" title="พรุ่งนี้"><i class="fas fa-calendar-plus"></i></button>
<button class="quick-btn" onclick="quickAction('แสดงนัดหมายทั้งหมด')" title="ทั้งหมด"><i class="fas fa-list"></i></button>
<button class="quick-btn" onclick="quickAction('นัดหมายสัปดาห์นี้')" title="สัปดาห์นี้"><i class="fas fa-calendar-week"></i></button>
</div>
<div class="chat-area" id="chatArea">
<div class="message bot">
<div class="message-content">
สวัสดีค่ะ! ยินดีต้อนรับสู่ระบบจัดการนัดหมาย <i class="fas fa-calendar-alt"></i><br><br>
คุณสามารถ:<br>
<i class="fas fa-microphone"></i> พูดหรือพิมพ์เพื่อบันทึกนัดหมาย<br>
<i class="fas fa-eye"></i> ดูรายการนัดหมาย<br>
<i class="fas fa-edit"></i> แก้ไขหรือลบนัดหมาย<br><br>
ลองบอกเราเลยว่าคุณต้องการนัดอะไร <i class="fas fa-smile"></i>
</div>
</div>
</div>
<div class="input-area">
<div class="input-group">
<input type="text" id="messageInput" placeholder="พิมพ์ข้อความ..." autocomplete="off">
<button id="voiceBtn" onclick="toggleVoiceInput()" title="กดค้างเพื่อพูด"><i class="fas fa-microphone"></i></button>
<button onclick="sendMessage()" title="ส่งข้อความ"><i class="fas fa-paper-plane"></i></button>
</div>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
// ตัวแปรสำหรับจัดการสถานะ
let db;
let isOnline = navigator.onLine;
let recognition;
let isRecording = false;
let conversationState = {
step: 'idle',
tempAppointment: {}
};
let userId = localStorage.getItem('userId') || 'user_' + Date.now();
localStorage.setItem('userId', userId);
// เริ่มต้น IndexedDB
function initDB() {
const request = indexedDB.open('AppointmentDB', 1);
request.onerror = () => console.error('Database failed to open');
request.onsuccess = () => {
db = request.result;
console.log('Database opened successfully');
syncWithServer();
};
request.onupgradeneeded = (e) => {
db = e.target.result;
if (!db.objectStoreNames.contains('appointments')) {
const appointmentStore = db.createObjectStore('appointments', {keyPath: 'id', autoIncrement: true});
appointmentStore.createIndex('date', 'date', {unique: false});
appointmentStore.createIndex('synced', 'synced', {unique: false});
appointmentStore.createIndex('userId', 'userId', {unique: false});
}
if (!db.objectStoreNames.contains('pendingSync')) {
db.createObjectStore('pendingSync', {keyPath: 'id', autoIncrement: true});
}
};
}
// ระบบ Speech Recognition
function initSpeechRecognition() {
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.lang = 'th-TH';
recognition.continuous = false;
recognition.interimResults = false;
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
document.getElementById('messageInput').value = transcript;
sendMessage();
};
recognition.onerror = (event) => {
console.error('Speech recognition error:', event.error);
stopRecording();
};
recognition.onend = () => {
stopRecording();
};
}
}
function toggleVoiceInput() {
if (!recognition) {
showNotification('เบราว์เซอร์ของคุณไม่รองรับการรับเสียง', 'error');
return;
}
if (isRecording) {
recognition.stop();
} else {
recognition.start();
isRecording = true;
document.getElementById('voiceBtn').classList.add('recording');
addBotMessage('<i class="fas fa-microphone pulse"></i> กำลังฟัง...');
}
}
function stopRecording() {
isRecording = false;
document.getElementById('voiceBtn').classList.remove('recording');
}
// ฟังก์ชันส่งข้อความ
async function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message) return;
addUserMessage(message);
input.value = '';
showTypingIndicator();
// ประมวลผลข้อความด้วย AI หรือ Offline
await processMessage(message);
}
async function processMessage(message) {
const lowerMsg = message.toLowerCase();
// ตรวจสอบคำสั่งพื้นฐาน
if (lowerMsg.includes('แสดง') || lowerMsg.includes('ดู') || lowerMsg.includes('นัดหมาย')) {
if (lowerMsg.includes('วันนี้')) {
await showAppointments('today');
} else if (lowerMsg.includes('พรุ่งนี้')) {
await showAppointments('tomorrow');
} else if (lowerMsg.includes('สัปดาห์')) {
await showAppointments('week');
} else {
await showAppointments('all');
}
return;
}
// ตรวจสอบว่ากำลังบันทึกนัดหมายแบบทีละขั้นตอนหรือไม่
if (conversationState.step === 'idle') {
// ลองแยกข้อมูลนัดหมายอัตโนมัติ
const appointmentData = await extractAppointmentData(message);
if (appointmentData.complete) {
await saveAppointment(appointmentData);
} else {
// เริ่มกระบวนการบันทึกแบบทีละขั้นตอน
conversationState.step = 'title';
conversationState.tempAppointment = appointmentData;
if (!appointmentData.title) {
hideTypingIndicator();
addBotMessage('เรื่องอะไรคะ? (เช่น ประชุมทีม, พบหมอ, เจอเพื่อน)');
} else if (!appointmentData.date) {
conversationState.step = 'date';
hideTypingIndicator();
addBotMessage('นัดวันไหนคะ? (เช่น วันนี้, พรุ่งนี้, 30 ธันวาคม)');
} else if (!appointmentData.time) {
conversationState.step = 'time';
hideTypingIndicator();
addBotMessage('เวลากี่โมงคะ? (เช่น 14:00, 2 โมงเย็น)');
}
}
} else {
// กำลังอยู่ในกระบวนการบันทึก
await handleStepByStep(message);
}
}
async function handleStepByStep(message) {
switch (conversationState.step) {
case 'title':
conversationState.tempAppointment.title = message;
conversationState.step = 'date';
hideTypingIndicator();
addBotMessage('นัดวันไหนคะ? (เช่น วันนี้, พรุ่งนี้, 30 ธันวาคม)');
break;
case 'date':
const date = parseDate(message);
if (date) {
conversationState.tempAppointment.date = date;
conversationState.step = 'time';
hideTypingIndicator();
addBotMessage('เวลากี่โมงคะ? (เช่น 14:00, 2 โมงเย็น)');
} else {
hideTypingIndicator();
addBotMessage('ขอโทษค่ะ ไม่เข้าใจวันที่ กรุณาบอกใหม่ค่ะ (เช่น วันนี้, พรุ่งนี้, 30/12/2025)');
}
break;
case 'time':
const time = parseTime(message);
if (time) {
conversationState.tempAppointment.time = time;
conversationState.step = 'location';
hideTypingIndicator();
addBotMessage('นัดที่ไหนคะ? (ถ้าไม่มีให้พิมพ์ "-" หรือ "ไม่มี")');
} else {
hideTypingIndicator();
addBotMessage('ขอโทษค่ะ ไม่เข้าใจเวลา กรุณาบอกใหม่ค่ะ (เช่น 14:00, 2 โมงเย็น)');
}
break;
case 'location':
conversationState.tempAppointment.location = message === '-' || message.includes('ไม่มี') ? '' : message;
await saveAppointment(conversationState.tempAppointment);
conversationState.step = 'idle';
conversationState.tempAppointment = {};
break;
}
}
async function extractAppointmentData(message) {
// ถ้า online ให้ส่งไปยัง AI ประมวลผล
if (isOnline) {
try {
const response = await fetch('api.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
action: 'extract',
message: message,
userId: userId
})
});
const data = await response.json();
if (data.success) {
return data.appointment;
}
} catch (error) {
console.error('AI extraction failed:', error);
}
}
// Fallback: แยกข้อมูลแบบ offline
return extractAppointmentOffline(message);
}
function extractAppointmentOffline(message) {
const data = {complete: false};
// แยกวันที่
const dateMatch = message.match(/วันนี้|พรุ่งนี้|มะรืน|\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/i);
if (dateMatch) {
data.date = parseDate(dateMatch[0]);
}
// แยกเวลา
const timeMatch = message.match(/\d{1,2}[:\.]\d{2}|\d{1,2}\s*(โมง|นาฬิกา)/i);
if (timeMatch) {
data.time = parseTime(timeMatch[0]);
}
// ถ้ามีทั้งวันที่และเวลา ให้ใช้ข้อความทั้งหมดเป็น title
if (data.date && data.time) {
data.title = message;
data.location = '';
data.complete = true;
}
return data;
}
function parseDate(dateStr) {
const today = new Date();
const lower = dateStr.toLowerCase();
if (lower.includes('วันนี้')) {
return today.toISOString().split('T')[0];
} else if (lower.includes('พรุ่งนี้')) {
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow.toISOString().split('T')[0];
} else if (lower.includes('มะรืน')) {
const dayAfter = new Date(today);
dayAfter.setDate(dayAfter.getDate() + 2);
return dayAfter.toISOString().split('T')[0];
}
// รูปแบบ DD/MM/YYYY
const match = dateStr.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/);
if (match) {
let [, day, month, year] = match;
if (year.length === 2) year = '20' + year;
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
}
return null;
}
function parseTime(timeStr) {
const match = timeStr.match(/(\d{1,2})[:\.]*(\d{2})?/);
if (match) {
let [, hour, minute = '00'] = match;
// ตรวจสอบบ่าย/เย็น
if (timeStr.includes('บ่าย') || timeStr.includes('เย็น')) {
hour = parseInt(hour);
if (hour < 12) hour += 12;
}
return `${String(hour).padStart(2, '0')}:${minute}`;
}
return null;
}
async function saveAppointment(data) {
const appointment = {
userId: userId,
title: data.title,
date: data.date,
time: data.time,
location: data.location || '',
createdAt: new Date().toISOString(),
synced: false
};
// บันทึกใน IndexedDB
const transaction = db.transaction(['appointments'], 'readwrite');
const store = transaction.objectStore('appointments');
const request = store.add(appointment);
request.onsuccess = async (e) => {
appointment.id = e.target.result;
hideTypingIndicator();
addBotMessage(`<i class="fas fa-check-circle" style="color: #10b981;"></i> บันทึกนัดหมายเรียบร้อยแล้วค่ะ!`);
const card = `
<div class="appointment-card">
<div class="title">${appointment.title}</div>
<div class="detail"><i class="fas fa-calendar-alt"></i> ${formatDate(appointment.date)}</div>
<div class="detail"><i class="fas fa-clock"></i> ${appointment.time} น.</div>
${appointment.location ? `<div class="detail"><i class="fas fa-map-marker-alt"></i> ${appointment.location}</div>` : ''}
</div>
`;
addBotMessage(card);
// Sync กับ server ถ้า online
if (isOnline) {
await syncAppointmentToServer(appointment);
} else {
addToPendingSync(appointment);
}
// ตั้งการแจ้งเตือน
scheduleNotification(appointment);
};
}
async function syncAppointmentToServer(appointment) {
try {
const response = await fetch('api.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
action: 'save',
appointment: appointment
})
});
const data = await response.json();
if (data.success) {
// อัพเดทสถานะ synced
const transaction = db.transaction(['appointments'], 'readwrite');
const store = transaction.objectStore('appointments');
appointment.synced = true;
appointment.serverId = data.id;
store.put(appointment);
}
} catch (error) {
console.error('Sync failed:', error);
addToPendingSync(appointment);
}
}
function addToPendingSync(appointment) {
const transaction = db.transaction(['pendingSync'], 'readwrite');
const store = transaction.objectStore('pendingSync');
store.add(appointment);
}
async function syncWithServer() {
if (!isOnline) return;
// ดึงข้อมูลที่ยังไม่ได้ sync
const transaction = db.transaction(['pendingSync'], 'readwrite');
const store = transaction.objectStore('pendingSync');
const request = store.getAll();
request.onsuccess = async () => {
const pending = request.result;
for (const appointment of pending) {
await syncAppointmentToServer(appointment);
store.delete(appointment.id);
}
};
}
async function showAppointments(filter) {
const transaction = db.transaction(['appointments'], 'readonly');
const store = transaction.objectStore('appointments');
const index = store.index('userId');
const request = index.getAll(userId);
request.onsuccess = () => {
let appointments = request.result;
const today = new Date().toISOString().split('T')[0];
// กรองตามเงื่อนไข
if (filter === 'today') {
appointments = appointments.filter(a => a.date === today);
} else if (filter === 'tomorrow') {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const tomorrowStr = tomorrow.toISOString().split('T')[0];
appointments = appointments.filter(a => a.date === tomorrowStr);
} else if (filter === 'week') {
const weekLater = new Date();
weekLater.setDate(weekLater.getDate() + 7);
appointments = appointments.filter(a => a.date <= weekLater.toISOString().split('T')[0]);
}
hideTypingIndicator();
if (appointments.length === 0) {
addBotMessage('ไม่มีนัดหมายค่ะ');
return;
}
appointments.sort((a, b) => new Date(a.date + ' ' + a.time) - new Date(b.date + ' ' + b.time));
let message = `พบ ${appointments.length} นัดหมาย:<br>`;
appointments.forEach(a => {
message += `
<div class="appointment-card">
<div class="title">${a.title}</div>
<div class="detail"><i class="fas fa-calendar-alt"></i> ${formatDate(a.date)}</div>
<div class="detail"><i class="fas fa-clock"></i> ${a.time} น.</div>
${a.location ? `<div class="detail"><i class="fas fa-map-marker-alt"></i> ${a.location}</div>` : ''}
</div>
`;
});
addBotMessage(message);
};
}
function formatDate(dateStr) {
const date = new Date(dateStr);
const thaiMonths = ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'];
return `${date.getDate()} ${thaiMonths[date.getMonth()]} ${date.getFullYear() + 543}`;
}
function scheduleNotification(appointment) {
if ('Notification' in window && Notification.permission === 'granted') {
const appointmentTime = new Date(appointment.date + ' ' + appointment.time);
const notifyTime = appointmentTime - (30 * 60 * 1000); // แจ้งเตือนก่อน 30 นาที
const now = Date.now();
if (notifyTime > now) {
setTimeout(() => {
new Notification('เตือนนัดหมาย', {
body: `${appointment.title}\nเวลา ${appointment.time} น.`,
icon: '/icon-192.png',
badge: '/icon-192.png'
});
}, notifyTime - now);
}
}
}
function requestNotificationPermission() {
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}
}
// ฟังก์ชันแสดงข้อความ
function addUserMessage(text) {
const chatArea = document.getElementById('chatArea');
const messageDiv = document.createElement('div');
messageDiv.className = 'message user';
messageDiv.innerHTML = `<div class="message-content">${text}</div>`;
chatArea.appendChild(messageDiv);
chatArea.scrollTop = chatArea.scrollHeight;
}
function addBotMessage(text) {
const chatArea = document.getElementById('chatArea');
const typingIndicator = document.querySelector('.typing-indicator');
if (typingIndicator) typingIndicator.remove();
const messageDiv = document.createElement('div');
messageDiv.className = 'message bot';
messageDiv.innerHTML = `<div class="message-content">${text}</div>`;
chatArea.appendChild(messageDiv);
chatArea.scrollTop = chatArea.scrollHeight;
}
function showTypingIndicator() {
const chatArea = document.getElementById('chatArea');
const typingDiv = document.createElement('div');
typingDiv.className = 'message bot';
typingDiv.innerHTML = `
<div class="typing-indicator show">
<span></span>
<span></span>
<span></span>
</div>
`;
chatArea.appendChild(typingDiv);
chatArea.scrollTop = chatArea.scrollHeight;
}
function hideTypingIndicator() {
const typingIndicator = document.querySelector('.typing-indicator');
if (typingIndicator) {
typingIndicator.parentElement.remove();
}
}
function quickAction(text) {
document.getElementById('messageInput').value = text;
sendMessage();
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = 'notification show ' + type;
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// อัพเดทสถานะ Online/Offline
function updateOnlineStatus() {
isOnline = navigator.onLine;
const statusIcon = document.getElementById('statusIcon');
const statusText = document.getElementById('statusText');
if (isOnline) {
statusIcon.classList.remove('offline');
statusIcon.className = 'fas fa-wifi';
statusText.textContent = 'Online';
syncWithServer();
} else {
statusIcon.classList.add('offline');
statusIcon.className = 'fas fa-wifi-slash';
statusText.textContent = 'Offline';
}
}
// Event Listeners
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
document.getElementById('messageInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
// Service Worker สำหรับ PWA
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js')
.then(reg => console.log('Service Worker registered', reg))
.catch(err => console.error('Service Worker registration failed', err));
}
// เริ่มต้นระบบ
window.addEventListener('DOMContentLoaded', () => {
initDB();
initSpeechRecognition();
updateOnlineStatus();
requestNotificationPermission();
});
</script>
</body>
</html>