feat: ticket views, statuses, participants, merge; mobile layout fixes (#5)
- New migrations: ticket views, statuses, participants, merge support - New models: TicketView, TicketStatus, TicketParticipant - New seeder: EmailTemplatesSeeder - Console commands for ticketing - Mobile: sidebar min-w-0/overflow-hidden, tab nav overflow-x-auto Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace Dashboard\Ticketing\Http\Controllers;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Dashboard\Ticketing\Models\EmailConnection;
|
||||
use Dashboard\Ticketing\Models\PriorityLevel;
|
||||
use Dashboard\Ticketing\Models\TicketingAgentAccess;
|
||||
use Dashboard\Ticketing\Models\TicketingGroup;
|
||||
use Dashboard\Ticketing\Models\TicketingProject;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Dashboard\Ticketing\Models\TicketStatus;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -77,7 +78,7 @@ class TicketingSettingsController extends Controller
|
||||
|
||||
$agents = $isBootstrap
|
||||
? collect()
|
||||
: TicketingAgentAccess::whereIn('group_id', $myGroupIds)->get();
|
||||
: TicketingAgentAccess::whereIn('group_id', $myGroupIds)->with('group')->get();
|
||||
|
||||
if ($agents->isNotEmpty()) {
|
||||
$agentUserIds = $agents->pluck('user_id')->unique();
|
||||
@@ -87,8 +88,7 @@ class TicketingSettingsController extends Controller
|
||||
|
||||
$priorities = $isBootstrap
|
||||
? collect()
|
||||
: PriorityLevel::where(fn($q) => $q->whereNull('group_id')->orWhereIn('group_id', $myGroupIds))
|
||||
->orderBy('sort_order')->get();
|
||||
: PriorityLevel::orderBy('sort_order')->get();
|
||||
|
||||
$projects = $isBootstrap
|
||||
? collect()
|
||||
@@ -101,14 +101,16 @@ class TicketingSettingsController extends Controller
|
||||
: EmailConnection::whereIn('group_id', $myGroupIds)->get();
|
||||
|
||||
return Inertia::render('Ticketing/Settings', [
|
||||
'groups' => $groups,
|
||||
'agents' => $agents,
|
||||
'priorities' => $priorities,
|
||||
'projects' => $projects,
|
||||
'groups' => $groups,
|
||||
'agents' => $agents,
|
||||
'priorities' => $priorities,
|
||||
'projects' => $projects,
|
||||
'ticketStatuses' => TicketStatus::orderBy('sort_order')->get(),
|
||||
'emailConnections' => $emailConnections,
|
||||
'myGroupIds' => $myGroupIds,
|
||||
'isBootstrap' => $isBootstrap,
|
||||
'isSiteAdmin' => $this->isSiteAdmin(),
|
||||
'myGroupIds' => $myGroupIds,
|
||||
'isBootstrap' => $isBootstrap,
|
||||
'isSiteAdmin' => $this->isSiteAdmin(),
|
||||
'autoCloseDays' => (int) Setting::get('ticketing.auto_close_days', 0),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -150,11 +152,10 @@ class TicketingSettingsController extends Controller
|
||||
];
|
||||
foreach ($defaults as $d) {
|
||||
PriorityLevel::create([
|
||||
'group_id' => $group->id,
|
||||
'name' => $d['name'],
|
||||
'color' => $d['color'],
|
||||
'name' => $d['name'],
|
||||
'color' => $d['color'],
|
||||
'description' => null,
|
||||
'sort_order' => $d['sort_order'],
|
||||
'sort_order' => $d['sort_order'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -209,21 +210,17 @@ class TicketingSettingsController extends Controller
|
||||
|
||||
public function storePriority(Request $request)
|
||||
{
|
||||
$this->requireAgentAccess();
|
||||
if (!$this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage global priorities.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'color' => 'required|string|regex:/^#[0-9a-fA-F]{6}$/',
|
||||
'name' => 'required|string|max:100',
|
||||
'color' => 'required|string|regex:/^#[0-9a-fA-F]{6}$/',
|
||||
'description' => 'nullable|string',
|
||||
'sort_order' => 'integer|min:0',
|
||||
'group_id' => 'nullable|exists:ticketing_groups,id',
|
||||
'sort_order' => 'integer|min:0',
|
||||
]);
|
||||
|
||||
// If group_id given, caller must be manager of that group
|
||||
if (!empty($validated['group_id'])) {
|
||||
$this->requireManagerAccess($validated['group_id']);
|
||||
}
|
||||
|
||||
PriorityLevel::create($validated);
|
||||
|
||||
return back()->with('success', 'Priority level created.');
|
||||
@@ -231,33 +228,17 @@ class TicketingSettingsController extends Controller
|
||||
|
||||
public function updatePriority(Request $request, PriorityLevel $priority)
|
||||
{
|
||||
$this->requireAgentAccess();
|
||||
|
||||
if ($priority->group_id) {
|
||||
$this->requireManagerAccess($priority->group_id);
|
||||
} else {
|
||||
// Global priorities require site admin
|
||||
if (!$this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage global priorities.');
|
||||
}
|
||||
if (!$this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage global priorities.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'color' => 'required|string|regex:/^#[0-9a-fA-F]{6}$/',
|
||||
'name' => 'required|string|max:100',
|
||||
'color' => 'required|string|regex:/^#[0-9a-fA-F]{6}$/',
|
||||
'description' => 'nullable|string',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'group_id' => [
|
||||
'nullable',
|
||||
'exists:ticketing_groups,id',
|
||||
Rule::in([$priority->group_id, null]),
|
||||
],
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
]);
|
||||
|
||||
if (!empty($validated['group_id'])) {
|
||||
$this->requireManagerAccess($validated['group_id']);
|
||||
}
|
||||
|
||||
$priority->update($validated);
|
||||
|
||||
return back()->with('success', 'Priority level updated.');
|
||||
@@ -265,15 +246,8 @@ class TicketingSettingsController extends Controller
|
||||
|
||||
public function destroyPriority(PriorityLevel $priority)
|
||||
{
|
||||
$this->requireAgentAccess();
|
||||
|
||||
if ($priority->group_id) {
|
||||
$this->requireManagerAccess($priority->group_id);
|
||||
} else {
|
||||
// Global priorities require site admin
|
||||
if (!$this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage global priorities.');
|
||||
}
|
||||
if (!$this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage global priorities.');
|
||||
}
|
||||
|
||||
if ($priority->tickets()->exists()) {
|
||||
@@ -400,4 +374,97 @@ class TicketingSettingsController extends Controller
|
||||
$connection->delete();
|
||||
return back()->with('success', 'Email connection removed.');
|
||||
}
|
||||
|
||||
public function storeStatus(Request $request)
|
||||
{
|
||||
if (! $this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage ticket statuses.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'color' => 'required|string|regex:/^#[0-9a-fA-F]{6}$/',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'is_closed' => 'boolean',
|
||||
]);
|
||||
|
||||
// Derive a slug from the name (lowercase, underscores) — must be unique
|
||||
$slug = \Str::slug($validated['name'], '_');
|
||||
if (TicketStatus::where('slug', $slug)->exists()) {
|
||||
$slug = $slug . '_' . time();
|
||||
}
|
||||
|
||||
TicketStatus::create([
|
||||
'slug' => $slug,
|
||||
'name' => $validated['name'],
|
||||
'color' => $validated['color'],
|
||||
'sort_order' => $validated['sort_order'],
|
||||
'is_closed' => $request->boolean('is_closed', false),
|
||||
'is_system' => false,
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Status created.');
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, TicketStatus $status)
|
||||
{
|
||||
if (! $this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage ticket statuses.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'color' => 'required|string|regex:/^#[0-9a-fA-F]{6}$/',
|
||||
'sort_order' => 'required|integer|min:0',
|
||||
'is_closed' => 'boolean',
|
||||
]);
|
||||
|
||||
// System statuses: only allow name, color, sort_order to be changed — not is_closed
|
||||
$update = [
|
||||
'name' => $validated['name'],
|
||||
'color' => $validated['color'],
|
||||
'sort_order' => $validated['sort_order'],
|
||||
];
|
||||
if (! $status->is_system) {
|
||||
$update['is_closed'] = $request->boolean('is_closed', false);
|
||||
}
|
||||
|
||||
$status->update($update);
|
||||
|
||||
return back()->with('success', 'Status updated.');
|
||||
}
|
||||
|
||||
public function destroyStatus(TicketStatus $status)
|
||||
{
|
||||
if (! $this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage ticket statuses.');
|
||||
}
|
||||
|
||||
if ($status->is_system) {
|
||||
return back()->withErrors(['status' => 'System statuses cannot be deleted.']);
|
||||
}
|
||||
|
||||
if ($status->tickets()->exists()) {
|
||||
return back()->withErrors(['status' => 'Cannot delete a status that is in use by tickets.']);
|
||||
}
|
||||
|
||||
$status->delete();
|
||||
|
||||
return back()->with('success', 'Status removed.');
|
||||
}
|
||||
|
||||
public function updateAutomation(Request $request)
|
||||
{
|
||||
if (! $this->isSiteAdmin()) {
|
||||
abort(403, 'Only site admins can manage automation settings.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'auto_close_days' => 'required|integer|min:0|max:365',
|
||||
]);
|
||||
|
||||
Setting::set('ticketing.auto_close_days', $validated['auto_close_days']);
|
||||
|
||||
return back()->with('success', 'Automation settings saved.');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user