# راهنمای کامل و جامع پیاده‌سازی سیستم تیکتینگ (ربات تلگرام + مینی اپ React)

این سند شامل تمام اطلاعات، ساختار دیتابیس، اندپوینت‌های API، منطق سورس‌کد ربات (PHP) و کدهای فرانت‌اند (React Mini App) برای انتقال کامل و بدون کم و کسر بخش سیستم پشتیبانی و تیکتینگ به پروژه‌ها یا ربات‌های دیگر است.

---

## ۱. ساختار دیتابیس (Database Schema)

برای ذخیره‌سازی تیکت‌ها و پیام‌های مربوطه از دو جدول اصلی `chats` و `chats_info` و یک تنظیم در جدول `setting` استفاده می‌شود.

```sql
-- ۱. جدول اصلی تیکت‌ها (موضوع و وضعیت تیکت)
CREATE TABLE IF NOT EXISTS `chats` (
  `id` INT(255) NOT NULL AUTO_INCREMENT,
  `user_id` BIGINT(10) NOT NULL,            -- آیدی عددی تلگرام کاربر
  `create_date` INT(255) NOT NULL,          -- زمان ایجاد (Timestamp)
  `title` VARCHAR(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, -- عنوان تیکت
  `category` VARCHAR(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, -- دسته‌بندی
  `state` INT(5) NOT NULL DEFAULT 0,        -- 0: جدید/در انتظار پاسخ، 1: پاسخ داده شده، 2: بسته شده
  `rate` INT(5) NOT NULL DEFAULT 0,         -- امتیاز ثبت شده توسط کاربر (1 تا 5)
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ۲. جدول پیام‌های تیکت (گفتگوها)
CREATE TABLE IF NOT EXISTS `chats_info` (
  `id` INT(255) NOT NULL AUTO_INCREMENT,
  `chat_id` INT(255) NOT NULL,              -- شناسه تیکت (مرتبط با chats.id)
  `sent_date` INT(255) NOT NULL,            -- زمان ارسال (Timestamp)
  `msg_type` VARCHAR(50) DEFAULT NULL,       -- نوع پیام: 'user' (کاربر) یا 'admin' (پشتیبان)
  `text` VARCHAR(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, -- متن یا لینک عکس/فایل
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ۳. دسته‌بندی‌های تیکت (در جدول تنظیمات general)
-- نمونه رکورد برای دسته‌بندی تیکت‌ها:
INSERT INTO `setting` (`type`, `value`) VALUES ('TICKETS_CATEGORY', 'پشتیبانی فنی');
INSERT INTO `setting` (`type`, `value`) VALUES ('TICKETS_CATEGORY', 'بخش مالی');
INSERT INTO `setting` (`type`, `value`) VALUES ('TICKETS_CATEGORY', 'شکایت');
```

---

## ۲. اندپوینت‌های Backend API (فایل `api.php`)

اگر از مینی‌اپ استفاده می‌کنید، نیازمند اندپوینت‌های زیر در بخش API ربات خود هستید:

```php
<?php
// نمونه ساختار هندل کردن اکشن‌های تیکت در api.php

switch ($action) {

    // 1. دریافت لیست تیکت‌ها
    case 'tickets':
        if ($isAdmin) {
            // ادمین: همه تیکت‌ها به همراه عکس پروفایل کاربر
            $tickRes = $connection->query("SELECT c.*, u.name as user_name, u.userid as user_tg_id FROM `chats` c LEFT JOIN `users` u ON c.user_id = u.userid ORDER BY c.id DESC");
            $tickets = $tickRes ? $tickRes->fetch_all(MYSQLI_ASSOC) : [];
        } else {
            // کاربر عادی: فقط تیکت‌های خودش
            $tickStmt = $connection->prepare("SELECT * FROM `chats` WHERE `user_id` = ? ORDER BY `id` DESC");
            $tickStmt->bind_param("i", $userId);
            $tickStmt->execute();
            $tickets = $tickStmt->get_result()->fetch_all(MYSQLI_ASSOC);
            $tickStmt->close();
        }
        echo json_encode(['status' => 'success', 'tickets' => $tickets]);
        break;

    // 2. دریافت پیام‌های یک تیکت مشخص
    case 'ticket_messages':
        $chatId = intval($_POST['ticket_id'] ?? $_GET['ticket_id'] ?? 0);
        
        // برسی مالکیت تیکت
        $chk = $connection->prepare("SELECT * FROM `chats` WHERE `id` = ?");
        $chk->bind_param("i", $chatId);
        $chk->execute();
        $ticketInfo = $chk->get_result()->fetch_assoc();
        $chk->close();

        if (!$ticketInfo || (!$isAdmin && $ticketInfo['user_id'] != $userId)) {
            echo json_encode(['status' => 'error', 'message' => 'دسترسی غیرمجاز']);
            exit();
        }

        $msgStmt = $connection->prepare("SELECT * FROM `chats_info` WHERE `chat_id` = ? ORDER BY `id` ASC");
        $msgStmt->bind_param("i", $chatId);
        $msgStmt->execute();
        $messages = $msgStmt->get_result()->fetch_all(MYSQLI_ASSOC);
        $msgStmt->close();

        echo json_encode([
            'status' => 'success',
            'ticket' => $ticketInfo,
            'messages' => $messages
        ]);
        break;

    // 3. ایجاد تیکت جدید از طریق مینی اپ
    case 'create_ticket':
        $title = trim($_POST['title'] ?? '');
        $category = trim($_POST['category'] ?? 'عمومی');
        $text = trim($_POST['text'] ?? '');
        $time = time();

        if (empty($title) || empty($text)) {
            echo json_encode(['status' => 'error', 'message' => 'عنوان و متن تیکت الزامی است']);
            exit();
        }

        // اپلود فایل پیوست در صورت وجود
        $fileUrl = null;
        if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
            $ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
            $fileName = 'ticket_' . $time . '_' . rand(1000,9999) . '.' . $ext;
            $uploadDir = __DIR__ . '/uploads/tickets/';
            if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
            move_uploaded_file($_FILES['file']['tmp_name'], $uploadDir . $fileName);
            $fileUrl = 'uploads/tickets/' . $fileName;
        }

        // ثبت تیکت
        $stmt = $connection->prepare("INSERT INTO `chats` (`user_id`, `create_date`, `title`, `category`, `state`, `rate`) VALUES (?, ?, ?, ?, 0, 0)");
        $stmt->bind_param("iiss", $userId, $time, $title, $category);
        $stmt->execute();
        $chatId = $stmt->insert_id;
        $stmt->close();

        // ثبت اولین پیام
        $fullMsg = $text;
        if ($fileUrl) {
            $fullMsg .= "\n📎 [پیوست]($fileUrl)";
        }

        $mStmt = $connection->prepare("INSERT INTO `chats_info` (`chat_id`, `sent_date`, `msg_type`, `text`) VALUES (?, ?, 'user', ?)");
        $mStmt->bind_param("iis", $chatId, $time, $fullMsg);
        $mStmt->execute();
        $mStmt->close();

        // اطلاع‌رسانی به ادمین تلگرام
        $adminMsg = "📩 <b>تیکت جدید دریافت شد!</b>\n\n👤 کاربر: <code>$userId</code>\n🏷 موضوع: $category\n📌 عنوان: $title\n\n💬 متن: $text";
        // ارسال پیام با bot API telegram به ادمین ...

        echo json_encode(['status' => 'success', 'ticket_id' => $chatId]);
        break;

    // 4. ارسال پاسخ تیکت (توسط کاربر یا ادمین)
    case 'send_ticket_reply':
        $chatId = intval($_POST['ticket_id'] ?? 0);
        $text = trim($_POST['text'] ?? '');
        $time = time();
        $msgType = $isAdmin ? 'admin' : 'user';

        if (empty($text)) {
            echo json_encode(['status' => 'error', 'message' => 'متن پاسخ خالی است']);
            exit();
        }

        // درج پیام
        $mStmt = $connection->prepare("INSERT INTO `chats_info` (`chat_id`, `sent_date`, `msg_type`, `text`) VALUES (?, ?, ?, ?)");
        $mStmt->bind_param("iiss", $chatId, $time, $msgType, $text);
        $mStmt->execute();
        $mStmt->close();

        // به‌روزرسانی وضعیت تیکت: اگر ادمین جواب داد state=1، اگر کاربر جواب داد state=0 (در انتظار پاسخ)
        $newState = $isAdmin ? 1 : 0;
        $uStmt = $connection->prepare("UPDATE `chats` SET `state` = ? WHERE `id` = ?");
        $uStmt->bind_param("ii", $newState, $chatId);
        $uStmt->execute();
        $uStmt->close();

        // ارسال ناتیفیکیشن در تلگرام به طرف مقابل
        // ... (ارسال پیام تلگرامی)

        echo json_encode(['status' => 'success']);
        break;

    // 5. بستن تیکت
    case 'close_ticket':
        $chatId = intval($_POST['ticket_id'] ?? 0);
        $rate = intval($_POST['rate'] ?? 0);

        $stmt = $connection->prepare("UPDATE `chats` SET `state` = 2, `rate` = ? WHERE `id` = ?");
        $stmt->bind_param("ii", $rate, $chatId);
        $stmt->execute();
        $stmt->close();

        echo json_encode(['status' => 'success']);
        break;
}
?>
```

---

## ۳. منطق سمت ربات تلگرام (PHP Telegram Handler)

### الف) بخش کاربر (`handlers/user_tickets.php`)

```php
<?php
// هندلر تیکت برای کاربر در ربات

// ۱. ورود به منوی پشتیبانی
if ($data == "supportSection") {
    editText(
        $message_id,
        "به بخش پشتیبانی خوش آمدید 🛂\nلطفاً یکی از گزینه‌های زیر را انتخاب نمایید:",
        json_encode(['inline_keyboard' => [
            [['text' => "✉️ ثبت تیکت جدید", 'callback_data' => "usersNewTicket"]],
            [['text' => "تیکت‌های باز 📨", 'callback_data' => "usersOpenTickets"], ['text' => "📮 همه تیکت‌ها", 'callback_data' => "userAllTickets"]],
            [getMiniAppBtn("مشاهده تیکت‌ها در مینی‌اپ 📱", "tickets")],
            [getBtn('back_button', 'mainMenu')]
        ]])
    );
}

// ۲. انتخاب دسته‌بندی
if ($data == "usersNewTicket") {
    $stmt = $connection->prepare("SELECT * FROM `setting` WHERE `type` = 'TICKETS_CATEGORY'");
    $stmt->execute();
    $categories = $stmt->get_result();
    $stmt->close();
    
    $keys = [];
    $temp = [];
    while ($row = $categories->fetch_assoc()) {
        $catName = $row['value'];
        $temp[] = ['text' => $catName, 'callback_data' => "supportCat_$catName"];
        if (count($temp) == 2) {
            $keys[] = $temp;
            $temp = [];
        }
    }
    if (!empty($temp)) $keys[] = $temp;
    $keys[] = [getBtn('back_button', 'supportSection')];

    editText($message_id, "💠 لطفاً واحد مورد نظر خود را انتخاب نمایید:", json_encode(['inline_keyboard' => $keys]));
}

// ۳. دریافت عنوان تیکت
if (preg_match('/^supportCat_(.*)/', $data, $match)) {
    $ticketCat = $match[1];
    setUser("getTicketTitle_" . $ticketCat);
    delMessage();
    sendMessage("🔘 لطفاً عنوان تیکت خود را ارسال کنید:", $cancelKey);
}

if (preg_match('/^getTicketTitle_(.*)/', $userInfo['step'] ?? '', $match) && $text != $buttonValues['cancel']) {
    $ticketCat = $match[1];
    if (trim($text) == "") {
        sendMessage("⚠️ عنوان تیکت نمی‌تواند خالی باشد.");
        exit();
    }
    setUser("sendTicket_" . $ticketCat);
    setUser($text, 'temp'); // ذخیره عنوان در ستون temp
    sendMessage("🔘 لطفاً توضیحات تیکت خود را ارسال کنید (می‌توانید عکس، ویدیو، وویس یا فایل نیز بفرستید):");
}

// ۴. دریافت متن/فایل و ثبت نهایی تیکت
if (preg_match('/^sendTicket_(.*)/', $userInfo['step'] ?? '', $match) && $text != $buttonValues['cancel']) {
    $ticketCat = $match[1];
    $ticketTitle = mb_substr($userInfo['temp'], 0, 100);
    $time = time();

    // ثبت در جدول اصلی chats
    $stmt = $connection->prepare("INSERT INTO `chats` (`user_id`, `create_date`, `title`, `category`, `state`, `rate`) VALUES (?, ?, ?, ?, 0, 0)");
    $stmt->bind_param("iiss", $from_id, $time, $ticketTitle, $ticketCat);
    $stmt->execute();
    $chatRowId = $stmt->insert_id;
    $stmt->close();

    // ثبت در جدول پیام‌ها chats_info
    $msgText = $text ?: ($caption ?: "ارسال فایل پیوست");
    $stmtMsg = $connection->prepare("INSERT INTO `chats_info` (`chat_id`, `sent_date`, `msg_type`, `text`) VALUES (?, ?, 'user', ?)");
    $stmtMsg->bind_param("iis", $chatRowId, $time, $msgText);
    $stmtMsg->execute();
    $stmtMsg->close();

    setUser('none');
    sendMessage("✅ تیکت شما با موفقیت ثبت شد و به پشتیبانی ارسال گردید.");

    // اطلاع‌رسانی به ادمین به همراه دکمه پاسخ سریع
    $adminKeys = json_encode(['inline_keyboard' => [
        [
            ['text' => "پاسخ در ربات ✍️", 'callback_data' => "reply_{$chatRowId}"],
            ['text' => "مشاهده در مینی‌اپ 📱", 'web_app' => ['url' => "https://yourdomain.com/mini-app/#tickets-{$chatRowId}"]]
        ]
    ]]);
    sendMessageCustom($admin, "📩 **تیکت جدید**\nاز کاربر: $from_id\nموضوع: $ticketCat\nعنوان: $ticketTitle\nمتن: $msgText", $adminKeys);
}
?>
```

### ب) بخش ادمین (`handlers/admin_tickets.php`)

```php
<?php
// هندلر مدیریت تیکت‌ها توسط ادمین

// پاسخ به تیکت توسط ادمین
if (preg_match('/^reply_(\d+)/', $data, $match) && $isAdmin) {
    $ticketId = $match[1];
    setUser("adminReplyTicket_" . $ticketId);
    sendMessage("✍️ لطفاً پاسخ خود را برای تیکت کد #$ticketId ارسال کنید:", $cancelKey);
}

if (preg_match('/^adminReplyTicket_(\d+)/', $userInfo['step'] ?? '', $match) && $text != $buttonValues['cancel'] && $isAdmin) {
    $ticketId = $match[1];
    $time = time();

    // ثبت پاسخ ادمین
    $stmt = $connection->prepare("INSERT INTO `chats_info` (`chat_id`, `sent_date`, `msg_type`, `text`) VALUES (?, ?, 'admin', ?)");
    $stmt->bind_param("iis", $ticketId, $time, $text);
    $stmt->execute();
    $stmt->close();

    // تغییر وضعیت تیکت به 1 (پاسخ داده شده)
    $stmtU = $connection->prepare("UPDATE `chats` SET `state` = 1 WHERE `id` = ?");
    $stmtU->bind_param("i", $ticketId);
    $stmtU->execute();
    $stmtU->close();

    // یافتن کاربر تیکت برای ارسال پیام
    $tStmt = $connection->prepare("SELECT `user_id`, `title` FROM `chats` WHERE `id` = ?");
    $tStmt->bind_param("i", $ticketId);
    $tStmt->execute();
    $tInfo = $tStmt->get_result()->fetch_assoc();
    $tStmt->close();

    setUser('none');
    sendMessage("✅ پاسخ شما با موفقیت برای کاربر ارسال شد.");

    if ($tInfo) {
        $userKeys = json_encode(['inline_keyboard' => [
            [['text' => "ادامه گفتگو 💬", 'callback_data' => "userViewTicket_$ticketId"]]
        ]]);
        sendMessageCustom($tInfo['user_id'], "📩 **پاسخ جدید از پشتیبانی**\nتیکت: {$tInfo['title']}\n\n💬 **پاسخ:** $text", $userKeys);
    }
}
?>
```

---

## ۴. کدهای فرانت‌اند مینی‌اپ React (کامپوننت کامل Ticket UI)

این کامپوننت React کامل‌ترین بخش سیستم تیکتینگ شامل: لیست تیکت‌ها، فیلتر (باز/بسته)، ساخت تیکت جدید با فایل پیوست، نمایش چت زنده، ارسال پاسخ و ثبت امتیاز است:

```jsx
import React, { useState, useEffect, useRef } from 'react';
import { Ticket, Plus, Send, Copy, Check, ArrowRight, AlertTriangle, Sparkles } from 'lucide-react';

export default function TicketSystem({ apiBaseUrl, initData }) {
  const [activeTab, setActiveTab] = useState('tickets'); // 'tickets', 'create', 'chat'
  const [tickets, setTickets] = useState([]);
  const [ticketFilter, setTicketFilter] = useState('open'); // 'open', 'closed'
  const [activeTicket, setActiveTicket] = useState(null);
  const [ticketMessages, setTicketMessages] = useState([]);
  const [loading, setLoading] = useState(false);

  // Form States
  const [newTitle, setNewTitle] = useState('');
  const [newCategory, setNewCategory] = useState('عمومی');
  const [newText, setNewText] = useState('');
  const [newFile, setNewFile] = useState(null);
  const [chatInput, setChatInput] = useState('');
  
  // Rating State
  const [showRatingModal, setShowRatingModal] = useState(false);
  const [ratingValue, setRatingValue] = useState(5);

  const messagesEndRef = useRef(null);

  // دریافت لیست تیکت‌ها
  const fetchTickets = async () => {
    setLoading(true);
    try {
      const formData = new FormData();
      formData.append('action', 'tickets');
      formData.append('initData', initData);

      const res = await fetch(`${apiBaseUrl}/api.php`, { method: 'POST', body: formData });
      const data = await res.json();
      if (data.status === 'success') {
        setTickets(data.tickets || []);
      }
    } catch (err) {
      console.error("Error fetching tickets:", err);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchTickets();
  }, []);

  // باز کردن چت تیکت
  const openTicketChat = async (ticket) => {
    setActiveTicket(ticket);
    setActiveTab('chat');
    setLoading(true);
    try {
      const formData = new FormData();
      formData.append('action', 'ticket_messages');
      formData.append('ticket_id', ticket.id);
      formData.append('initData', initData);

      const res = await fetch(`${apiBaseUrl}/api.php`, { method: 'POST', body: formData });
      const data = await res.json();
      if (data.status === 'success') {
        setTicketMessages(data.messages || []);
      }
    } catch (err) {
      console.error("Error fetching messages:", err);
    } finally {
      setLoading(false);
    }
  };

  // ارسال تیکت جدید
  const handleCreateTicket = async (e) => {
    e.preventDefault();
    if (!newTitle.trim() || !newText.trim()) return;

    setLoading(true);
    try {
      const formData = new FormData();
      formData.append('action', 'create_ticket');
      formData.append('title', newTitle);
      formData.append('category', newCategory);
      formData.append('text', newText);
      if (newFile) formData.append('file', newFile);
      formData.append('initData', initData);

      const res = await fetch(`${apiBaseUrl}/api.php`, { method: 'POST', body: formData });
      const data = await res.json();
      if (data.status === 'success') {
        setNewTitle('');
        setNewText('');
        setNewFile(null);
        await fetchTickets();
        setActiveTab('tickets');
      }
    } catch (err) {
      console.error("Error creating ticket:", err);
    } finally {
      setLoading(false);
    }
  };

  // ارسال پاسخ
  const handleSendReply = async () => {
    if (!chatInput.trim() || !activeTicket) return;

    const replyText = chatInput;
    setChatInput('');
    
    // آپدیت موقت UI (Optimistic Update)
    const tempMsg = { id: Date.now(), msg_type: 'user', text: replyText, sent_date: Math.floor(Date.now()/1000) };
    setTicketMessages(prev => [...prev, tempMsg]);

    try {
      const formData = new FormData();
      formData.append('action', 'send_ticket_reply');
      formData.append('ticket_id', activeTicket.id);
      formData.append('text', replyText);
      formData.append('initData', initData);

      await fetch(`${apiBaseUrl}/api.php`, { method: 'POST', body: formData });
    } catch (err) {
      console.error("Error sending reply:", err);
    }
  };

  // بستن تیکت و ثبت امتیاز
  const handleCloseTicket = async () => {
    if (!activeTicket) return;
    try {
      const formData = new FormData();
      formData.append('action', 'close_ticket');
      formData.append('ticket_id', activeTicket.id);
      formData.append('rate', ratingValue);
      formData.append('initData', initData);

      await fetch(`${apiBaseUrl}/api.php`, { method: 'POST', body: formData });
      setShowRatingModal(false);
      setActiveTab('tickets');
      fetchTickets();
    } catch (err) {
      console.error("Error closing ticket:", err);
    }
  };

  const filteredTickets = tickets.filter(t => ticketFilter === 'open' ? t.state !== 2 : t.state === 2);

  return (
    <div className="p-4 dir-rtl text-right text-white">
      {/* هدر بخش پشتیبانی */}
      <div className="flex items-center justify-between mb-4">
        <h2 className="text-xl font-bold flex items-center gap-2">
          <Ticket className="text-blue-400" /> پشتیبانی و تیکت‌ها
        </h2>
        {activeTab === 'tickets' && (
          <button 
            onClick={() => setActiveTab('create')}
            className="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1.5 rounded-lg flex items-center gap-1 text-sm font-medium transition"
          >
            <Plus size={16} /> تیکت جدید
          </button>
        )}
      </div>

      {/* ۱. تب لیست تیکت‌ها */}
      {activeTab === 'tickets' && (
        <div>
          <div className="flex gap-2 mb-4">
            <button 
              onClick={() => setTicketFilter('open')}
              className={`flex-1 py-2 rounded-xl text-sm font-semibold transition ${ticketFilter === 'open' ? 'bg-blue-600 text-white' : 'bg-slate-800 text-gray-400'}`}
            >
              تیکت‌های فعال
            </button>
            <button 
              onClick={() => setTicketFilter('closed')}
              className={`flex-1 py-2 rounded-xl text-sm font-semibold transition ${ticketFilter === 'closed' ? 'bg-blue-600 text-white' : 'bg-slate-800 text-gray-400'}`}
            >
              تیکت‌های بسته شده
            </button>
          </div>

          {filteredTickets.length === 0 ? (
            <div className="text-center py-10 bg-slate-800/50 rounded-2xl border border-slate-700/50">
              <Ticket size={40} className="mx-auto mb-2 text-gray-500" />
              <p className="text-gray-400 text-sm">هیچ تیکتی در این بخش یافت نشد.</p>
            </div>
          ) : (
            <div className="space-y-3">
              {filteredTickets.map(t => (
                <div 
                  key={t.id} 
                  onClick={() => openTicketChat(t)}
                  className="bg-slate-800 border border-slate-700/60 p-4 rounded-xl cursor-pointer hover:border-blue-500 transition"
                >
                  <div className="flex justify-between items-start mb-2">
                    <span className="font-bold text-base">{t.title}</span>
                    <span className={`text-xs px-2 py-0.5 rounded-full ${
                      t.state === 0 ? 'bg-yellow-500/20 text-yellow-400' :
                      t.state === 1 ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'
                    }`}>
                      {t.state === 0 ? 'در انتظار پاسخ' : t.state === 1 ? 'پاسخ داده شده' : 'بسته شده'}
                    </span>
                  </div>
                  <div className="flex justify-between text-xs text-gray-400">
                    <span>دسته‌بندی: {t.category}</span>
                    <span>کد تیکت: #{t.id}</span>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {/* ۲. تب ایجاد تیکت جدید */}
      {activeTab === 'create' && (
        <form onSubmit={handleCreateTicket} className="bg-slate-800 border border-slate-700/60 p-4 rounded-xl space-y-4">
          <div className="flex justify-between items-center mb-2">
            <h3 className="font-bold">ثبت تیکت جدید</h3>
            <button type="button" onClick={() => setActiveTab('tickets')} className="text-gray-400 hover:text-white">
              <ArrowRight size={20} />
            </button>
          </div>

          <div>
            <label className="block text-xs font-semibold mb-1 text-gray-300">عنوان تیکت</label>
            <input 
              type="text" 
              value={newTitle}
              onChange={e => setNewTitle(e.target.value)}
              placeholder="مثال: قطعی اکانت یا عدم اتصال"
              className="w-full bg-slate-900 border border-slate-700 rounded-lg p-2.5 text-sm focus:outline-none focus:border-blue-500"
              required
            />
          </div>

          <div>
            <label className="block text-xs font-semibold mb-1 text-gray-300">دسته‌بندی</label>
            <select 
              value={newCategory} 
              onChange={e => setNewCategory(e.target.value)}
              className="w-full bg-slate-900 border border-slate-700 rounded-lg p-2.5 text-sm focus:outline-none focus:border-blue-500"
            >
              <option value="پشتیبانی فنی">پشتیبانی فنی</option>
              <option value="بخش مالی">بخش مالی</option>
              <option value="پیشنهاد یا شکایت">پیشنهاد یا شکایت</option>
            </select>
          </div>

          <div>
            <label className="block text-xs font-semibold mb-1 text-gray-300">متن پیام</label>
            <textarea 
              rows={4}
              value={newText}
              onChange={e => setNewText(e.target.value)}
              placeholder="شرح کامل مشکل خود را وارد کنید..."
              className="w-full bg-slate-900 border border-slate-700 rounded-lg p-2.5 text-sm focus:outline-none focus:border-blue-500"
              required
            />
          </div>

          <div>
            <label className="block text-xs font-semibold mb-1 text-gray-300">پیوست فایل (اختیاری)</label>
            <input 
              type="file" 
              onChange={e => setNewFile(e.target.files[0])}
              className="w-full text-xs text-gray-400 file:bg-slate-700 file:text-white file:border-0 file:px-3 file:py-1.5 file:rounded-lg file:ml-2"
            />
          </div>

          <button 
            type="submit" 
            disabled={loading}
            className="w-full bg-blue-600 hover:bg-blue-500 py-3 rounded-xl font-bold transition disabled:opacity-50"
          >
            {loading ? 'در حال ثبت...' : 'ارسال تیکت'}
          </button>
        </form>
      )}

      {/* ۳. صفحه گفتگو (صفحه چت تیکت) */}
      {activeTab === 'chat' && activeTicket && (
        <div className="flex flex-col h-[75vh]">
          {/* هدر چت */}
          <div className="bg-slate-800 p-3 rounded-t-xl border border-slate-700 flex justify-between items-center">
            <div>
              <h3 className="font-bold text-sm">{activeTicket.title}</h3>
              <span className="text-xs text-gray-400">کد: #{activeTicket.id} | {activeTicket.category}</span>
            </div>
            <div className="flex items-center gap-2">
              {activeTicket.state !== 2 && (
                <button 
                  onClick={() => setShowRatingModal(true)}
                  className="bg-red-500/20 text-red-400 border border-red-500/30 text-xs px-2.5 py-1 rounded-lg"
                >
                  بستن تیکت
                </button>
              )}
              <button onClick={() => setActiveTab('tickets')} className="text-gray-400">
                <ArrowRight size={20} />
              </button>
            </div>
          </div>

          {/* متن پیام‌ها */}
          <div className="flex-1 overflow-y-auto bg-slate-900/80 p-3 border-x border-slate-700 space-y-3 dir-rtl">
            {ticketMessages.map(m => (
              <div 
                key={m.id} 
                className={`flex flex-col max-w-[80%] ${m.msg_type === 'user' ? 'mr-auto items-start' : 'ml-auto items-end'}`}
              >
                <div className={`p-3 rounded-2xl text-sm ${
                  m.msg_type === 'user' 
                    ? 'bg-blue-600 text-white rounded-br-none' 
                    : 'bg-slate-800 border border-slate-700 text-gray-200 rounded-bl-none'
                }`}>
                  <p className="whitespace-pre-wrap">{m.text}</p>
                </div>
                <span className="text-[10px] text-gray-500 mt-1">
                  {new Date(m.sent_date * 1000).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' })}
                </span>
              </div>
            ))}
            <div ref={messagesEndRef} />
          </div>

          {/* باکس ارسال پاسخ */}
          {activeTicket.state !== 2 ? (
            <div className="bg-slate-800 p-2 rounded-b-xl border border-slate-700 flex gap-2">
              <input 
                type="text" 
                value={chatInput}
                onChange={e => setChatInput(e.target.value)}
                onKeyDown={e => e.key === 'Enter' && handleSendReply()}
                placeholder="پاسخ خود را بنویسید..."
                className="flex-1 bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:outline-none"
              />
              <button 
                onClick={handleSendReply}
                className="bg-blue-600 hover:bg-blue-500 text-white p-2 rounded-lg"
              >
                <Send size={18} />
              </button>
            </div>
          ) : (
            <div className="bg-slate-800/80 p-3 rounded-b-xl border border-slate-700 text-center text-xs text-gray-400">
              این تیکت بسته شده است و امکان ارسال پاسخ جدید وجود ندارد.
            </div>
          )}
        </div>
      )}

      {/* مودال بستن تیکت و نظرسنجی */}
      {showRatingModal && (
        <div className="fixed inset-0 bg-black/70 flex items-center justify-center p-4 z-50">
          <div className="bg-slate-800 border border-slate-700 rounded-2xl p-5 w-full max-w-sm text-center">
            <Sparkles className="mx-auto text-yellow-400 mb-2" size={32} />
            <h3 className="font-bold text-lg mb-2">امتیاز به پشتیبانی</h3>
            <p className="text-xs text-gray-400 mb-4">لطفاً کیفیت پاسخگویی پشتیبانی را ارزیابی کنید:</p>

            <div className="flex justify-center gap-2 mb-6">
              {[1, 2, 3, 4, 5].map(star => (
                <button 
                  key={star} 
                  onClick={() => setRatingValue(star)}
                  className={`text-2xl transition ${ratingValue >= star ? 'scale-110 text-yellow-400' : 'opacity-30'}`}
                >
                  ⭐
                </button>
              ))}
            </div>

            <div className="flex gap-2">
              <button 
                onClick={handleCloseTicket}
                className="flex-1 bg-red-600 hover:bg-red-500 py-2 rounded-xl text-sm font-bold"
              >
                ثبت و بستن تیکت
              </button>
              <button 
                onClick={() => setShowRatingModal(false)}
                className="flex-1 bg-slate-700 py-2 rounded-xl text-sm"
              >
                انصراف
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
```

---

## ۵. چک‌لیست و مراحل پیاده‌سازی برای AI Agent

اگر این مستند را به ایجنت AI دیگر می‌دهید، دستورالعمل زیر را برای اجرا صادر کنید:

1. **دیتابیس**: جدول‌های SQL فوق (`chats` و `chats_info` و مقادیر اولیه `TICKETS_CATEGORY` در جدول `setting`) را در دیتابیس اجرا کند.
2. **بک‌اند**: اکشن‌های معرفی شده در فایل `api.php` را در API ربات اضافه/مچ کند.
3. **ربات تلگرام**: کدهای هندلر کاربر (`user_tickets.php`) و ادمین (`admin_tickets.php`) را در پروژه ربات قرار داده و مسیرهای `include` را در فایل اصلی ربات (`bot.php`) فراخوانی کند.
4. **فرانت‌اند مینی‌اپ**: کامپوننت UI ارائه شده را در React وارد کرده و استیت `activeTab === 'tickets'` را به آن متصل نماید.
