feat: initial dashboard-ticketing scaffold
This commit is contained in:
36
src/Http/Controllers/TicketCommentController.php
Normal file
36
src/Http/Controllers/TicketCommentController.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Dashboard\Ticketing\Http\Controllers;
|
||||
|
||||
use Dashboard\Ticketing\Models\Ticket;
|
||||
use Dashboard\Ticketing\Models\TicketComment;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
class TicketCommentController extends Controller
|
||||
{
|
||||
public function store(Request $request, Ticket $ticket)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
|
||||
abort_if(! $isAdmin && $ticket->user_id !== $user->id, 403);
|
||||
|
||||
$request->validate([
|
||||
'body' => 'required|string',
|
||||
'is_internal' => 'boolean',
|
||||
]);
|
||||
|
||||
// Non-admins cannot post internal notes
|
||||
$isInternal = $isAdmin && $request->boolean('is_internal');
|
||||
|
||||
TicketComment::create([
|
||||
'ticket_id' => $ticket->id,
|
||||
'user_id' => $user->id,
|
||||
'body' => $request->body,
|
||||
'is_internal' => $isInternal,
|
||||
]);
|
||||
|
||||
return redirect()->route('ticketing.show', $ticket)->with('success', 'Comment added.');
|
||||
}
|
||||
}
|
||||
156
src/Http/Controllers/TicketController.php
Normal file
156
src/Http/Controllers/TicketController.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Dashboard\Ticketing\Http\Controllers;
|
||||
|
||||
use Dashboard\Ticketing\Models\Ticket;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
|
||||
$query = Ticket::with(['submitter:id,name', 'assignee:id,name']);
|
||||
|
||||
if (! $isAdmin) {
|
||||
$query->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
if ($search = $request->get('search')) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('title', 'like', "%{$search}%")
|
||||
->orWhere('description', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
if ($status = $request->get('status')) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
if ($priority = $request->get('priority')) {
|
||||
$query->where('priority', $priority);
|
||||
}
|
||||
|
||||
if ($category = $request->get('category')) {
|
||||
$query->where('category', $category);
|
||||
}
|
||||
|
||||
$tickets = $query->latest()->paginate(25)->withQueryString();
|
||||
|
||||
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,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Ticketing/Create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'required|string',
|
||||
'category' => 'required|in:IT,Facilities,HR,Other',
|
||||
'priority' => 'required|in:low,medium,high,urgent',
|
||||
]);
|
||||
|
||||
Ticket::create([
|
||||
'user_id' => auth()->id(),
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'category' => $request->category,
|
||||
'priority' => $request->priority,
|
||||
'status' => 'open',
|
||||
]);
|
||||
|
||||
return redirect()->route('ticketing.index')->with('success', 'Ticket submitted.');
|
||||
}
|
||||
|
||||
public function show(Ticket $ticket)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
|
||||
abort_if(! $isAdmin && $ticket->user_id !== $user->id, 403);
|
||||
|
||||
$ticket->load([
|
||||
'submitter:id,name',
|
||||
'assignee:id,name',
|
||||
'comments.author:id,name',
|
||||
]);
|
||||
|
||||
return Inertia::render('Ticketing/Show', [
|
||||
'ticket' => $ticket,
|
||||
'isAdmin' => $isAdmin,
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Ticket $ticket)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdmin = in_array($user->role ?? '', ['admin', 'super_admin']);
|
||||
|
||||
abort_if(! $isAdmin && $ticket->user_id !== $user->id, 403);
|
||||
|
||||
return Inertia::render('Ticketing/Edit', [
|
||||
'ticket' => $ticket,
|
||||
'isAdmin' => $isAdmin,
|
||||
]);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
$rules = [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'required|string',
|
||||
'category' => 'required|in:IT,Facilities,HR,Other',
|
||||
'priority' => 'required|in:low,medium,high,urgent',
|
||||
];
|
||||
|
||||
if ($isAdmin) {
|
||||
$rules['status'] = 'required|in:open,in_progress,resolved,closed';
|
||||
$rules['assigned_to'] = 'nullable|exists:users,id';
|
||||
}
|
||||
|
||||
$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.');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
$ticket->delete();
|
||||
|
||||
return redirect()->route('ticketing.index')->with('success', 'Ticket deleted.');
|
||||
}
|
||||
}
|
||||
35
src/Models/Ticket.php
Normal file
35
src/Models/Ticket.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Dashboard\Ticketing\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Ticket extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'assigned_to',
|
||||
'title',
|
||||
'description',
|
||||
'category',
|
||||
'priority',
|
||||
'status',
|
||||
];
|
||||
|
||||
public function submitter(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function assignee(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'assigned_to');
|
||||
}
|
||||
|
||||
public function comments(): HasMany
|
||||
{
|
||||
return $this->hasMany(TicketComment::class);
|
||||
}
|
||||
}
|
||||
30
src/Models/TicketComment.php
Normal file
30
src/Models/TicketComment.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Dashboard\Ticketing\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class TicketComment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'user_id',
|
||||
'body',
|
||||
'is_internal',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_internal' => 'boolean',
|
||||
];
|
||||
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class);
|
||||
}
|
||||
|
||||
public function author(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
19
src/TicketingServiceProvider.php
Normal file
19
src/TicketingServiceProvider.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Dashboard\Ticketing;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class TicketingServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
|
||||
$this->loadRoutesFrom(__DIR__.'/routes/ticketing.php');
|
||||
}
|
||||
}
|
||||
16
src/routes/ticketing.php
Normal file
16
src/routes/ticketing.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Dashboard\Ticketing\Http\Controllers\TicketController;
|
||||
use Dashboard\Ticketing\Http\Controllers\TicketCommentController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(['web', 'auth', 'app.access:ticketing'])->prefix('app/ticketing')->name('ticketing.')->group(function () {
|
||||
Route::get('/', [TicketController::class, 'index'])->name('index');
|
||||
Route::get('/create', [TicketController::class, 'create'])->name('create');
|
||||
Route::post('/', [TicketController::class, 'store'])->name('store');
|
||||
Route::get('/{ticket}', [TicketController::class, 'show'])->name('show');
|
||||
Route::get('/{ticket}/edit', [TicketController::class, 'edit'])->name('edit');
|
||||
Route::put('/{ticket}', [TicketController::class, 'update'])->name('update');
|
||||
Route::delete('/{ticket}', [TicketController::class, 'destroy'])->name('destroy');
|
||||
Route::post('/{ticket}/comments', [TicketCommentController::class, 'store'])->name('comments.store');
|
||||
});
|
||||
Reference in New Issue
Block a user