Added ticketing feature

This commit is contained in:
m.razaghi
2026-07-31 18:54:09 +03:30
parent d77605db75
commit 516bc4b26b
8 changed files with 406 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\TicketMessage;
use Illuminate\Http\Request;
class TicketController extends Controller
{
public function index(Request $request)
{
// Eager load the user who created the ticket
$query = Ticket::query()->with('user');
if ($request->filled('q')) {
$q = $request->q;
$query->where(function ($qry) use ($q) {
$qry->where('subject', 'like', "%{$q}%")
->orWhereHas('user', fn ($u) => $u->where('mobile', 'like', "%{$q}%")
->orWhere('name', 'like', "%{$q}%"));
});
}
// Optional: Filter by status (open, answered, user_reply, closed)
if ($request->filled('status')) {
$query->where('status', $request->status);
}
$perPage = min(max((int) $request->input('per_page', 15), 10), 100);
// Order by updated_at so tickets with new replies jump to the top
$tickets = $query->latest('updated_at')->paginate($perPage)->withQueryString();
return view('admin.tickets.index', compact('tickets'));
}
public function show(Ticket $ticket)
{
// Load messages and the user belonging to the ticket
$ticket->load(['messages.user', 'user']);
return view('admin.tickets.show', compact('ticket'));
}
public function reply(Request $request, Ticket $ticket)
{
$request->validate([
'message' => 'required|string',
'attachment' => 'nullable|file|mimes:jpg,jpeg,png,pdf|max:5120',
]);
$attachmentPath = null;
if ($request->hasFile('attachment')) {
$attachmentPath = $request->file('attachment')->store('tickets', 'public');
}
TicketMessage::create([
'ticket_id' => $ticket->id,
'user_id' => auth()->id(), // This will be the Admin's ID
'message' => $request->message,
'attachment_path' => $attachmentPath,
]);
$ticket->update(['status' => 'answered']);
return redirect()->route('tickets.show', $ticket->id)
->with('success', 'پاسخ شما با موفقیت ارسال شد.');
}
public function close(Ticket $ticket)
{
$ticket->update(['status' => 'closed']);
return redirect()->route('tickets.index')
->with('success', 'تیکت با موفقیت بسته شد.');
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Http\Controllers\api;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Traits\BaseApiResponse;
use Illuminate\Http\Request;
class TicketApiController extends Controller
{
use BaseApiResponse;
public function index()
{
$tickets = Ticket::where('user_id', auth()->id())
->orderBy('updated_at', 'desc')
->get()
->map(function ($ticket) {
return [
'id' => $ticket->id,
'subject' => $ticket->subject,
'status' => $ticket->status,
'priority' => $ticket->priority,
'created_at' => $ticket->created_at,
'updated_at' => $ticket->updated_at,
];
});
return $this->success($tickets, 'موفق', 'لیست تیکت‌ها با موفقیت دریافت شد');
}
public function show($id)
{
$ticket = Ticket::with(['messages' => function ($query) {
$query->orderBy('created_at', 'asc');
}])->where('user_id', auth()->id())->find($id);
if (!$ticket) {
return $this->failed([], 'یافت نشد', 'تیکت مورد نظر یافت نشد.', 404);
}
$formattedTicket = [
'id' => $ticket->id,
'subject' => $ticket->subject,
'status' => $ticket->status,
'priority' => $ticket->priority,
'created_at' => $ticket->created_at,
'messages' => $ticket->messages->map(function ($msg) {
return [
'id' => $msg->id,
// If user_id matches the logged in user, it's their message. Otherwise, it's admin's reply.
'is_me' => $msg->user_id === auth()->id(),
'message' => $msg->message,
'attachment_url' => $msg->attachment_path ? asset('storage/' . $msg->attachment_path) : null,
'created_at' => $msg->created_at,
];
}),
];
return $this->success($formattedTicket, 'موفق', 'جزئیات تیکت دریافت شد');
}
public function store(Request $request)
{
$validated = $request->validate([
'subject' => 'required|string|max:255',
'priority' => 'nullable|in:low,medium,high',
'message' => 'required|string',
'attachment' => 'nullable|file|mimes:jpg,jpeg,png,pdf|max:5120', // Max 5MB
]);
$ticket = Ticket::create([
'user_id' => auth()->id(),
'subject' => $validated['subject'],
'status' => 'open',
'priority' => $validated['priority'] ?? 'medium',
]);
$attachmentPath = null;
if ($request->hasFile('attachment')) {
$attachmentPath = $request->file('attachment')->store('tickets', 'public');
}
TicketMessage::create([
'ticket_id' => $ticket->id,
'user_id' => auth()->id(),
'message' => $validated['message'],
'attachment_path' => $attachmentPath,
]);
return $this->success(['ticket_id' => $ticket->id], 'موفق', 'تیکت شما با موفقیت ثبت شد');
}
public function reply(Request $request, $id)
{
$validated = $request->validate([
'message' => 'required|string',
'attachment' => 'nullable|file|mimes:jpg,jpeg,png,pdf|max:5120',
]);
$ticket = Ticket::where('user_id', auth()->id())->find($id);
if (!$ticket) {
return $this->failed([], 'خطا', 'تیکت یافت نشد', 404);
}
$attachmentPath = null;
if ($request->hasFile('attachment')) {
$attachmentPath = $request->file('attachment')->store('tickets', 'public');
}
TicketMessage::create([
'ticket_id' => $ticket->id,
'user_id' => auth()->id(),
'message' => $validated['message'],
'attachment_path' => $attachmentPath,
]);
$ticket->update(['status' => 'user_reply']);
return $this->success([], 'موفق', 'پاسخ شما با موفقیت ثبت شد');
}
}