validate([ 'status' => ['nullable', 'array'], 'status.*' => ['string', 'in:open,pending,resolved,closed'], 'room' => ['nullable', 'string', 'max:50'], 'customer_id' => ['nullable', 'integer'], 'date_from' => ['nullable', 'date'], 'date_to' => ['nullable', 'date'], 'search' => ['nullable', 'string', 'max:255'], ]); $query = Ticket::query() ->with(['customer']) ->latest('id') ->when($filters['status'] ?? null, fn ($q, $status) => $q->whereIn('status', $status)) ->when($filters['room'] ?? null, fn ($q, $room) => $q->where('room', $room)) ->when($filters['customer_id'] ?? null, fn ($q, $id) => $q->where('customer_id', $id)) ->when($filters['date_from'] ?? null, fn ($q, $from) => $q->whereDate('created_at', '>=', $from)) ->when($filters['date_to'] ?? null, fn ($q, $to) => $q->whereDate('created_at', '<=', $to)) ->when($filters['search'] ?? null, fn ($q, $search) => $q->where( fn ($q) => $q->where('subject', 'like', "%{$search}%")->orWhere('ticket_num', 'like', "%{$search}%") )); $paginated = $query->paginate($request->input('per_page', 15)); return response()->json([ 'error' => false, 'data' => [ 'items' => TicketResource::collection($paginated->items()), 'total' => $paginated->total(), 'per_page' => $paginated->perPage(), 'current_page' => $paginated->currentPage(), 'last_page' => $paginated->lastPage(), ], ]); } /** * Show a ticket with its replies and customer info. */ public function show(string $ticket_num) { $ticket = Ticket::query() ->with(['reply.user', 'customer']) ->where('ticket_num', $ticket_num) ->first(); if (!$ticket) { return response()->json([ 'error' => true, 'message' => 'Ticket không tồn tại.', ], 404); } return response()->json([ 'error' => false, 'data' => new TicketResource($ticket), ]); } /** * Update ticket status. */ public function updateStatus(Request $request, string $ticket_num) { $request->validate([ 'status' => ['required', 'string', 'in:open,pending,resolved,closed'], ]); $ticket = Ticket::query()->where('ticket_num', $ticket_num)->first(); if (!$ticket) { return response()->json([ 'error' => true, 'message' => 'Ticket không tồn tại.', ], 404); } $ticket->update(['status' => $request->input('status')]); return response()->json([ 'error' => false, 'message' => 'Trạng thái ticket đã được cập nhật.', 'data' => new TicketResource($ticket->fresh()), ]); } /** * Admin replies to a ticket. */ public function reply(Request $request, string $ticket_num) { $request->validate([ 'message' => ['required', 'string'], ]); $admin = auth('admin')->user(); $ticket = Ticket::query()->where('ticket_num', $ticket_num)->first(); if (!$ticket) { return response()->json([ 'error' => true, 'message' => 'Ticket không tồn tại.', ], 404); } $reply = $this->ticketReplyRepository->create([ 'ticket_id' => $ticket->id, 'user_id' => $admin->id, 'message' => $request->input('message'), 'customer_id' => $ticket->customer_id, ]); Notification::notifyCustomer($ticket->customer, [ 'type' => 'ticket_reply', 'title' => 'Admin vừa phản hồi ticket của bạn', 'body' => $ticket->ticket_num . ' - ' . $ticket->subject, 'link' => '/tickets/' . $ticket->ticket_num, 'data' => [ 'ticket_num' => $ticket->ticket_num, 'reply_id' => $reply->id, ], ]); // Update ticket status to pending after admin reply if ($ticket->status === 'open') { $ticket->update(['status' => 'pending']); } $reply->load('user'); broadcast(new TicketReplyCreated( ticketNum: $ticket->ticket_num, customerId: $ticket->customer_id, replyId: $reply->id, )); return response()->json([ 'error' => false, 'message' => 'Phản hồi đã được gửi.', 'data' => new TicketReplyResource($reply), ], 201); } }