feat: full dashboard-ticketing scaffold with data model, controllers, Vue pages
This commit is contained in:
@@ -2,155 +2,250 @@
|
||||
|
||||
namespace Dashboard\Ticketing\Http\Controllers;
|
||||
|
||||
use Dashboard\Ticketing\Models\PriorityLevel;
|
||||
use Dashboard\Ticketing\Models\Ticket;
|
||||
use Dashboard\Ticketing\Models\TicketingAgentAccess;
|
||||
use Dashboard\Ticketing\Models\TicketingGroup;
|
||||
use Dashboard\Ticketing\Models\TicketingProject;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
private function agentGroupIds(): array
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
return TicketingAgentAccess::where('user_id', Auth::id())
|
||||
->pluck('group_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
$query = Ticket::with(['submitter:id,name', 'assignee:id,name']);
|
||||
private function isAgent(int $groupId = null): bool
|
||||
{
|
||||
$query = TicketingAgentAccess::where('user_id', Auth::id());
|
||||
if ($groupId) {
|
||||
$query->where('group_id', $groupId);
|
||||
}
|
||||
return $query->exists();
|
||||
}
|
||||
|
||||
if (! $isAdmin) {
|
||||
$query->where('user_id', $user->id);
|
||||
private function isManager(int $groupId): bool
|
||||
{
|
||||
return TicketingAgentAccess::where('user_id', Auth::id())
|
||||
->where('group_id', $groupId)
|
||||
->where('role', 'manager')
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$user = Auth::user();
|
||||
$agentGroupIds = $this->agentGroupIds();
|
||||
$isAgent = count($agentGroupIds) > 0;
|
||||
|
||||
$query = Ticket::with(['group', 'priority', 'project']);
|
||||
|
||||
if ($isAgent) {
|
||||
$query->whereIn('group_id', $agentGroupIds);
|
||||
|
||||
// Filters
|
||||
if ($request->filled('group_id')) {
|
||||
$query->where('group_id', $request->group_id);
|
||||
}
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
if ($request->filled('priority_id')) {
|
||||
$query->where('priority_id', $request->priority_id);
|
||||
}
|
||||
if ($request->filter === 'mine') {
|
||||
$query->where('assigned_to', $user->id);
|
||||
} elseif ($request->filter === 'unassigned') {
|
||||
$query->whereNull('assigned_to');
|
||||
} elseif ($request->filter === 'pending') {
|
||||
$query->where('status', 'pending');
|
||||
} elseif (!$request->filled('status')) {
|
||||
$query->whereNotIn('status', ['resolved', 'closed']);
|
||||
}
|
||||
} else {
|
||||
$query->where('submitter_id', $user->id);
|
||||
}
|
||||
|
||||
if ($search = $request->get('search')) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('title', 'like', "%{$search}%")
|
||||
->orWhere('description', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
$tickets = $query->latest()->paginate(30)->withQueryString();
|
||||
|
||||
if ($status = $request->get('status')) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
// Enrich with submitter name
|
||||
$userIds = $tickets->pluck('submitter_id')->merge($tickets->pluck('assigned_to'))->filter()->unique();
|
||||
$users = \DB::table('users')->whereIn('id', $userIds)->get(['id', 'name', 'email'])->keyBy('id');
|
||||
|
||||
if ($priority = $request->get('priority')) {
|
||||
$query->where('priority', $priority);
|
||||
}
|
||||
$tickets->getCollection()->transform(function ($ticket) use ($users) {
|
||||
$ticket->submitter = $users[$ticket->submitter_id] ?? null;
|
||||
$ticket->assignee = $ticket->assigned_to ? ($users[$ticket->assigned_to] ?? null) : null;
|
||||
return $ticket;
|
||||
});
|
||||
|
||||
if ($category = $request->get('category')) {
|
||||
$query->where('category', $category);
|
||||
}
|
||||
|
||||
$tickets = $query->latest()->paginate(25)->withQueryString();
|
||||
$groups = TicketingGroup::when($isAgent, fn($q) => $q->whereIn('id', $agentGroupIds))->get();
|
||||
$priorities = PriorityLevel::whereNull('group_id')
|
||||
->orWhenIn('group_id', $agentGroupIds ?? [])
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
$projects = TicketingProject::when($isAgent, fn($q) => $q->whereIn('group_id', $agentGroupIds))
|
||||
->where('status', 'active')
|
||||
->get();
|
||||
|
||||
return Inertia::render('Ticketing/Index', [
|
||||
'tickets' => $tickets,
|
||||
'search' => $request->get('search', ''),
|
||||
'statusFilter' => $request->get('status', ''),
|
||||
'priorityFilter' => $request->get('priority', ''),
|
||||
'categoryFilter' => $request->get('category', ''),
|
||||
'isAdmin' => $isAdmin,
|
||||
'tickets' => $tickets,
|
||||
'groups' => $groups,
|
||||
'priorities' => $priorities,
|
||||
'projects' => $projects,
|
||||
'isAgent' => $isAgent,
|
||||
'filters' => $request->only(['group_id', 'status', 'priority_id', 'filter']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Ticketing/Create');
|
||||
$user = Auth::user();
|
||||
$agentGroupIds = $this->agentGroupIds();
|
||||
$isAgent = count($agentGroupIds) > 0;
|
||||
|
||||
$groups = TicketingGroup::when($isAgent, fn($q) => $q->whereIn('id', $agentGroupIds))->get();
|
||||
$priorities = PriorityLevel::orderBy('sort_order')->get();
|
||||
|
||||
return Inertia::render('Ticketing/Create', [
|
||||
'groups' => $groups,
|
||||
'priorities' => $priorities,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'required|string',
|
||||
'category' => 'required|in:IT,Facilities,HR,Other',
|
||||
'priority' => 'required|in:low,medium,high,urgent',
|
||||
'group_id' => 'required|exists:ticketing_groups,id',
|
||||
'priority_id' => 'nullable|exists:ticketing_priority_levels,id',
|
||||
'due_date' => 'nullable|date',
|
||||
]);
|
||||
|
||||
Ticket::create([
|
||||
'user_id' => auth()->id(),
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'category' => $request->category,
|
||||
'priority' => $request->priority,
|
||||
'status' => 'open',
|
||||
$group = TicketingGroup::findOrFail($validated['group_id']);
|
||||
$number = $group->nextTicketNumber();
|
||||
|
||||
$ticket = Ticket::create([
|
||||
...$validated,
|
||||
'number' => $number,
|
||||
'submitter_id' => Auth::id(),
|
||||
'status' => 'open',
|
||||
]);
|
||||
|
||||
return redirect()->route('ticketing.index')->with('success', 'Ticket submitted.');
|
||||
return redirect()->route('ticketing.show', $ticket);
|
||||
}
|
||||
|
||||
public function show(Ticket $ticket)
|
||||
public function show(Ticket $ticket): Response
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
$user = Auth::user();
|
||||
$isAgent = $this->isAgent($ticket->group_id);
|
||||
$isSubmitter = $ticket->submitter_id === $user->id;
|
||||
|
||||
abort_if(! $isAdmin && $ticket->user_id !== $user->id, 403);
|
||||
if (!$isAgent && !$isSubmitter) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$ticket->load([
|
||||
'submitter:id,name',
|
||||
'assignee:id,name',
|
||||
'comments.author:id,name',
|
||||
]);
|
||||
$ticket->load(['group', 'priority', 'project', 'messages', 'attachments']);
|
||||
|
||||
// Enrich messages with user data
|
||||
$userIds = $ticket->messages->pluck('user_id')->filter()->unique();
|
||||
$users = \DB::table('users')->whereIn('id', $userIds)->get(['id', 'name', 'email'])->keyBy('id');
|
||||
|
||||
$ticket->messages->each(function ($msg) use ($users) {
|
||||
$msg->author = $msg->user_id ? ($users[$msg->user_id] ?? null) : null;
|
||||
});
|
||||
|
||||
// Agents for assignment picker
|
||||
$agents = [];
|
||||
if ($isAgent) {
|
||||
$agentIds = TicketingAgentAccess::where('group_id', $ticket->group_id)->pluck('user_id');
|
||||
$agents = \DB::table('users')->whereIn('id', $agentIds)->get(['id', 'name', 'email']);
|
||||
}
|
||||
|
||||
$priorities = PriorityLevel::where(fn($q) => $q->whereNull('group_id')->orWhere('group_id', $ticket->group_id))
|
||||
->orderBy('sort_order')->get();
|
||||
|
||||
return Inertia::render('Ticketing/Show', [
|
||||
'ticket' => $ticket,
|
||||
'isAdmin' => $isAdmin,
|
||||
'ticket' => $ticket,
|
||||
'isAgent' => $isAgent,
|
||||
'isManager' => $isAgent && $this->isManager($ticket->group_id),
|
||||
'agents' => $agents,
|
||||
'priorities' => $priorities,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Ticket $ticket)
|
||||
public function edit(Ticket $ticket): Response
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
if (!$this->isAgent($ticket->group_id)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
abort_if(! $isAdmin && $ticket->user_id !== $user->id, 403);
|
||||
$ticket->load(['group', 'priority', 'project']);
|
||||
$priorities = PriorityLevel::where(fn($q) => $q->whereNull('group_id')->orWhere('group_id', $ticket->group_id))
|
||||
->orderBy('sort_order')->get();
|
||||
$agentIds = TicketingAgentAccess::where('group_id', $ticket->group_id)->pluck('user_id');
|
||||
$agents = \DB::table('users')->whereIn('id', $agentIds)->get(['id', 'name', 'email']);
|
||||
$projects = TicketingProject::where('group_id', $ticket->group_id)->get();
|
||||
|
||||
return Inertia::render('Ticketing/Edit', [
|
||||
'ticket' => $ticket,
|
||||
'isAdmin' => $isAdmin,
|
||||
'ticket' => $ticket,
|
||||
'priorities' => $priorities,
|
||||
'agents' => $agents,
|
||||
'projects' => $projects,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Ticket $ticket)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
|
||||
abort_if(! $isAdmin && $ticket->user_id !== $user->id, 403);
|
||||
if (!$this->isAgent($ticket->group_id)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'required|string',
|
||||
'category' => 'required|in:IT,Facilities,HR,Other',
|
||||
'priority' => 'required|in:low,medium,high,urgent',
|
||||
'title' => 'sometimes|required|string|max:255',
|
||||
'description' => 'sometimes|required|string',
|
||||
'status' => 'sometimes|in:open,in_progress,pending,resolved,closed',
|
||||
'priority_id' => 'nullable|exists:ticketing_priority_levels,id',
|
||||
'due_date' => 'nullable|date',
|
||||
'project_id' => 'nullable|exists:ticketing_projects,id',
|
||||
];
|
||||
|
||||
if ($isAdmin) {
|
||||
$rules['status'] = 'required|in:open,in_progress,resolved,closed';
|
||||
// assigned_to only settable by agents
|
||||
if ($this->isAgent($ticket->group_id)) {
|
||||
$rules['assigned_to'] = 'nullable|exists:users,id';
|
||||
}
|
||||
|
||||
$request->validate($rules);
|
||||
$ticket->update($request->validate($rules));
|
||||
|
||||
$data = $request->only(['title', 'description', 'category', 'priority']);
|
||||
|
||||
if ($isAdmin) {
|
||||
$data['status'] = $request->status;
|
||||
$data['assigned_to'] = $request->assigned_to;
|
||||
}
|
||||
|
||||
$ticket->update($data);
|
||||
|
||||
return redirect()->route('ticketing.show', $ticket)->with('success', 'Ticket updated.');
|
||||
return back()->with('success', 'Ticket updated.');
|
||||
}
|
||||
|
||||
public function destroy(Ticket $ticket)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
|
||||
abort_if(! $isAdmin && $ticket->user_id !== $user->id, 403);
|
||||
if (!$this->isManager($ticket->group_id)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$ticket->delete();
|
||||
return redirect()->route('ticketing.index');
|
||||
}
|
||||
|
||||
return redirect()->route('ticketing.index')->with('success', 'Ticket deleted.');
|
||||
public function myTickets(): Response
|
||||
{
|
||||
$tickets = Ticket::where('submitter_id', Auth::id())
|
||||
->with(['group', 'priority'])
|
||||
->latest()
|
||||
->paginate(20);
|
||||
|
||||
return Inertia::render('Ticketing/MyTickets', [
|
||||
'tickets' => $tickets,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user