new helpdesk module
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-zinc-200 text-sm dark:divide-zinc-700">
|
||||
<thead class="bg-zinc-50 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-300">
|
||||
<tr><th class="px-4 py-3">Name</th><th class="px-4 py-3">Slug</th><th class="px-4 py-3">Colour</th><th class="px-4 py-3">Tickets</th><th class="px-4 py-3 text-right">Actions</th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
|
||||
@forelse ($items as $item)
|
||||
<tr>
|
||||
<td class="px-4 py-3 font-medium text-zinc-900 dark:text-zinc-100">{{ $item->name }}</td>
|
||||
<td class="px-4 py-3 font-mono text-xs text-zinc-500">{{ $item->slug }}</td>
|
||||
<td class="px-4 py-3"><flux:badge color="{{ $item->color }}">{{ $item->color }}</flux:badge></td>
|
||||
<td class="px-4 py-3 text-zinc-500">{{ $item->tickets_count ?? 0 }}</td>
|
||||
<td class="px-4 py-3 text-right"><flux:button size="sm" wire:click="{{ $edit }}({{ $item->id }})" variant="ghost">Edit</flux:button><flux:button size="sm" wire:click="{{ $delete }}({{ $item->id }})" variant="danger">Delete</flux:button></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="5" class="px-4 py-10 text-center text-zinc-500">No records yet.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<div class="overflow-x-auto"><table class="min-w-full divide-y divide-zinc-200 text-sm dark:divide-zinc-700"><thead class="bg-zinc-50 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-300"><tr><th class="px-4 py-3">Project</th><th class="px-4 py-3">Team</th><th class="px-4 py-3">Slug</th><th class="px-4 py-3">Colour</th><th class="px-4 py-3">Tickets</th><th class="px-4 py-3 text-right">Actions</th></tr></thead><tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">@forelse ($items as $item)<tr><td class="px-4 py-3 font-medium text-zinc-900 dark:text-zinc-100">{{ $item->title }}</td><td class="px-4 py-3 text-zinc-500">{{ $item->team?->name ?? 'No team' }}</td><td class="px-4 py-3 font-mono text-xs text-zinc-500">{{ $item->slug }}</td><td class="px-4 py-3"><flux:badge color="{{ $item->color }}">{{ $item->color }}</flux:badge></td><td class="px-4 py-3 text-zinc-500">{{ $item->tickets_count }}</td><td class="px-4 py-3 text-right"><flux:button size="sm" wire:click="editProject({{ $item->id }})" variant="ghost">Edit</flux:button><flux:button size="sm" wire:click="deleteProject({{ $item->id }})" variant="danger">Delete</flux:button></td></tr>@empty<tr><td colspan="6" class="px-4 py-10 text-center text-zinc-500">No projects yet.</td></tr>@endforelse</tbody></table></div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<section class="overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="border-b border-zinc-200 bg-zinc-50 px-5 py-4 dark:border-zinc-700 dark:bg-zinc-800/70">
|
||||
<h2 class="text-lg font-semibold text-zinc-900 dark:text-white">{{ $title }}</h2>
|
||||
<p class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">{{ $description }}</p>
|
||||
</div>
|
||||
<div class="border-b border-zinc-200 p-5 dark:border-zinc-700">{{ $form }}</div>
|
||||
<div>{{ $slot }}</div>
|
||||
</section>
|
||||
@@ -0,0 +1 @@
|
||||
<div class="overflow-x-auto"><table class="min-w-full divide-y divide-zinc-200 text-sm dark:divide-zinc-700"><thead class="bg-zinc-50 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-300"><tr><th class="px-4 py-3">Team</th><th class="px-4 py-3">Slug</th><th class="px-4 py-3">Colour</th><th class="px-4 py-3">Projects</th><th class="px-4 py-3">Tickets</th><th class="px-4 py-3 text-right">Actions</th></tr></thead><tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">@forelse ($items as $item)<tr><td class="px-4 py-3 font-medium text-zinc-900 dark:text-zinc-100">{{ $item->name }}</td><td class="px-4 py-3 font-mono text-xs text-zinc-500">{{ $item->slug }}</td><td class="px-4 py-3"><flux:badge color="{{ $item->color }}">{{ $item->color }}</flux:badge></td><td class="px-4 py-3 text-zinc-500">{{ $item->projects_count }}</td><td class="px-4 py-3 text-zinc-500">{{ $item->tickets_count }}</td><td class="px-4 py-3 text-right"><flux:button size="sm" wire:click="editTeam({{ $item->id }})" variant="ghost">Edit</flux:button><flux:button size="sm" wire:click="deleteTeam({{ $item->id }})" variant="danger">Delete</flux:button></td></tr>@empty<tr><td colspan="6" class="px-4 py-10 text-center text-zinc-500">No teams yet.</td></tr>@endforelse</tbody></table></div>
|
||||
@@ -1,3 +1,22 @@
|
||||
<x-layouts.app :title="__('Helpdesk')">
|
||||
<livewire:helpdesk.ticket-index />
|
||||
<div class="space-y-6">
|
||||
<div class="overflow-hidden rounded-3xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="bg-gradient-to-br from-slate-950 via-slate-900 to-blue-950 px-6 py-7 text-white">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-blue-200">Support operations</p>
|
||||
<h1 class="mt-2 text-3xl font-semibold tracking-tight">Helpdesk</h1>
|
||||
<p class="mt-2 max-w-3xl text-sm text-blue-100/90">Manage tickets, priorities, statuses, categories, company teams and company projects from one professional workspace.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<flux:button :href="route('helpdesk.tickets.create')" wire:navigate variant="primary" icon="plus">New ticket</flux:button>
|
||||
<flux:button :href="route('helpdesk.settings')" wire:navigate variant="ghost" icon="cog-6-tooth">Settings</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<livewire:helpdesk.ticket-index />
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<div class="space-y-6" dusk="helpdesk-settings-manager">
|
||||
<div class="overflow-hidden rounded-3xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="bg-gradient-to-br from-slate-950 via-slate-900 to-blue-950 px-6 py-7 text-white">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-blue-200">Helpdesk administration</p>
|
||||
<h1 class="mt-2 text-3xl font-semibold tracking-tight">Support configuration</h1>
|
||||
<p class="mt-2 max-w-3xl text-sm text-blue-100/90">Manage the lookup data used by tickets, including statuses, priorities, categories, company teams and company projects.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<flux:button :href="route('helpdesk.index')" wire:navigate variant="ghost" icon="arrow-left">Helpdesk</flux:button>
|
||||
<flux:button :href="route('helpdesk.tickets.index')" wire:navigate variant="primary" icon="ticket">Tickets</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<flux:callout variant="success" icon="check-circle">{{ session('success') }}</flux:callout>
|
||||
@endif
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-5">
|
||||
@foreach ([
|
||||
'statuses' => ['label' => 'Statuses', 'count' => $statuses->count()],
|
||||
'priorities' => ['label' => 'Priorities', 'count' => $priorities->count()],
|
||||
'categories' => ['label' => 'Categories', 'count' => $categories->count()],
|
||||
'teams' => ['label' => 'Teams', 'count' => $teams->count()],
|
||||
'projects' => ['label' => 'Projects', 'count' => $projects->count()],
|
||||
] as $key => $item)
|
||||
<button type="button" wire:click="$set('section', '{{ $key }}')" class="rounded-2xl border px-4 py-3 text-left shadow-sm transition {{ $section === $key ? 'border-blue-500 bg-blue-50 text-blue-900 dark:border-blue-400 dark:bg-blue-950 dark:text-blue-100' : 'border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800' }}">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide opacity-70">{{ $item['label'] }}</div>
|
||||
<div class="mt-1 text-2xl font-semibold">{{ $item['count'] }}</div>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if ($section === 'statuses')
|
||||
<x-helpdesk::settings-section title="Statuses" description="Control workflow stages and mark which statuses close tickets.">
|
||||
<x-slot:form>
|
||||
<form wire:submit="saveStatus" class="grid gap-4 md:grid-cols-5">
|
||||
<flux:input wire:model="statusForm.name" label="Name" />
|
||||
<flux:input wire:model="statusForm.slug" label="Slug" placeholder="auto-generated" />
|
||||
<flux:input wire:model="statusForm.color" label="Badge colour" />
|
||||
<flux:input wire:model="statusForm.sort_order" label="Sort" type="number" min="0" />
|
||||
<label class="flex items-end gap-2 pb-2 text-sm text-zinc-700 dark:text-zinc-200"><input type="checkbox" wire:model="statusForm.is_closed" class="rounded border-zinc-300"> Closed status</label>
|
||||
<div class="md:col-span-5 flex gap-2"><flux:button type="submit" variant="primary">Save status</flux:button><flux:button type="button" wire:click="resetStatusForm" variant="ghost">Reset</flux:button></div>
|
||||
</form>
|
||||
</x-slot:form>
|
||||
<x-helpdesk::lookup-table :items="$statuses" edit="editStatus" delete="deleteStatus" />
|
||||
</x-helpdesk::settings-section>
|
||||
@endif
|
||||
|
||||
@if ($section === 'priorities')
|
||||
<x-helpdesk::settings-section title="Priorities" description="Define ticket urgency levels and display order.">
|
||||
<x-slot:form>
|
||||
<form wire:submit="savePriority" class="grid gap-4 md:grid-cols-4">
|
||||
<flux:input wire:model="priorityForm.name" label="Name" />
|
||||
<flux:input wire:model="priorityForm.slug" label="Slug" placeholder="auto-generated" />
|
||||
<flux:input wire:model="priorityForm.color" label="Badge colour" />
|
||||
<flux:input wire:model="priorityForm.sort_order" label="Sort" type="number" min="0" />
|
||||
<div class="md:col-span-4 flex gap-2"><flux:button type="submit" variant="primary">Save priority</flux:button><flux:button type="button" wire:click="resetPriorityForm" variant="ghost">Reset</flux:button></div>
|
||||
</form>
|
||||
</x-slot:form>
|
||||
<x-helpdesk::lookup-table :items="$priorities" edit="editPriority" delete="deletePriority" />
|
||||
</x-helpdesk::settings-section>
|
||||
@endif
|
||||
|
||||
@if ($section === 'categories')
|
||||
<x-helpdesk::settings-section title="Categories" description="Group tickets by support area, optionally per company.">
|
||||
<x-slot:form>
|
||||
<form wire:submit="saveCategory" class="grid gap-4 md:grid-cols-4">
|
||||
<flux:select wire:model="categoryForm.company_id" label="Company"><option value="">Global</option>@foreach ($companies as $company)<option value="{{ $company->id }}">{{ $company->name }}</option>@endforeach</flux:select>
|
||||
<flux:input wire:model="categoryForm.name" label="Name" />
|
||||
<flux:input wire:model="categoryForm.slug" label="Slug" placeholder="auto-generated" />
|
||||
<flux:input wire:model="categoryForm.color" label="Badge colour" />
|
||||
<div class="md:col-span-4"><flux:textarea wire:model="categoryForm.description" label="Description" rows="3" /></div>
|
||||
<div class="md:col-span-4 flex gap-2"><flux:button type="submit" variant="primary">Save category</flux:button><flux:button type="button" wire:click="resetCategoryForm" variant="ghost">Reset</flux:button></div>
|
||||
</form>
|
||||
</x-slot:form>
|
||||
<x-helpdesk::lookup-table :items="$categories" edit="editCategory" delete="deleteCategory" />
|
||||
</x-helpdesk::settings-section>
|
||||
@endif
|
||||
|
||||
@if ($section === 'teams')
|
||||
<x-helpdesk::settings-section title="Teams" description="Create support teams under each company and assign projects to them.">
|
||||
<x-slot:form>
|
||||
<form wire:submit="saveTeam" class="grid gap-4 md:grid-cols-4">
|
||||
<flux:select wire:model="teamForm.company_id" label="Company"><option value="">Global</option>@foreach ($companies as $company)<option value="{{ $company->id }}">{{ $company->name }}</option>@endforeach</flux:select>
|
||||
<flux:input wire:model="teamForm.name" label="Name" />
|
||||
<flux:input wire:model="teamForm.slug" label="Slug" placeholder="auto-generated" />
|
||||
<flux:input wire:model="teamForm.color" label="Badge colour" />
|
||||
<div class="md:col-span-4"><flux:textarea wire:model="teamForm.description" label="Description" rows="3" /></div>
|
||||
<div class="md:col-span-4 flex gap-2"><flux:button type="submit" variant="primary">Save team</flux:button><flux:button type="button" wire:click="resetTeamForm" variant="ghost">Reset</flux:button></div>
|
||||
</form>
|
||||
</x-slot:form>
|
||||
<x-helpdesk::team-table :items="$teams" />
|
||||
</x-helpdesk::settings-section>
|
||||
@endif
|
||||
|
||||
@if ($section === 'projects')
|
||||
<x-helpdesk::settings-section title="Projects" description="Create company projects and optionally assign them to support teams.">
|
||||
<x-slot:form>
|
||||
<form wire:submit="saveProject" class="grid gap-4 md:grid-cols-5">
|
||||
<flux:select wire:model="projectForm.company_id" label="Company"><option value="">Global</option>@foreach ($companies as $company)<option value="{{ $company->id }}">{{ $company->name }}</option>@endforeach</flux:select>
|
||||
<flux:select wire:model="projectForm.team_id" label="Team"><option value="">No team</option>@foreach ($teams as $team)<option value="{{ $team->id }}">{{ $team->name }}</option>@endforeach</flux:select>
|
||||
<flux:input wire:model="projectForm.title" label="Title" />
|
||||
<flux:input wire:model="projectForm.slug" label="Slug" placeholder="auto-generated" />
|
||||
<flux:input wire:model="projectForm.color" label="Badge colour" />
|
||||
<div class="md:col-span-5"><flux:textarea wire:model="projectForm.description" label="Description" rows="3" /></div>
|
||||
<div class="md:col-span-5 flex gap-2"><flux:button type="submit" variant="primary">Save project</flux:button><flux:button type="button" wire:click="resetProjectForm" variant="ghost">Reset</flux:button></div>
|
||||
</form>
|
||||
</x-slot:form>
|
||||
<x-helpdesk::project-table :items="$projects" />
|
||||
</x-helpdesk::settings-section>
|
||||
@endif
|
||||
</div>
|
||||
@@ -12,6 +12,21 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
||||
<flux:select wire:model="company_id" label="Company">
|
||||
<option value="">Default company</option>
|
||||
@foreach ($companies as $company)
|
||||
<option value="{{ $company->id }}">{{ $company->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model="team_id" label="Team">
|
||||
<option value="">No team</option>
|
||||
@foreach ($teams as $team)
|
||||
<option value="{{ $team->id }}">{{ $team->name }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model="priority_id" label="Priority" required>
|
||||
<option value="">Select priority</option>
|
||||
@foreach ($priorities as $priority)
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-medium text-zinc-900 dark:text-zinc-100">{{ $ticket->subject }}</div>
|
||||
<div class="text-xs text-zinc-500">{{ $ticket->category?->name ?? 'No category' }}</div>
|
||||
<div class="text-xs text-zinc-500">{{ $ticket->category?->name ?? 'No category' }} @if($ticket->project) · {{ $ticket->project->title }} @endif @if($ticket->team) · {{ $ticket->team->name }} @endif</div>
|
||||
</td>
|
||||
<td class="px-4 py-3"><flux:badge color="{{ $ticket->status->color }}">{{ $ticket->status->name }}</flux:badge></td>
|
||||
<td class="px-4 py-3"><flux:badge color="{{ $ticket->priority->color }}">{{ $ticket->priority->name }}</flux:badge></td>
|
||||
|
||||
Reference in New Issue
Block a user