index.html

32.24 KB
30/09/2025 02:11
HTML
<!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>