296 lines
22 KiB
PHP
296 lines
22 KiB
PHP
<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, company projects, Git repositories and workflow messages.</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-4 xl:grid-cols-8">
|
|
@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()],
|
|
'boards' => ['label' => 'Boards', 'count' => $boards->count()],
|
|
'git' => ['label' => 'Git', 'count' => $repositories->count()],
|
|
'workflow' => ['label' => 'Workflow', 'count' => $workflowTemplates->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
|
|
|
|
|
|
@if ($section === 'boards')
|
|
<x-helpdesk::settings-section title="Kanban Boards" description="Create configurable workflow boards and choose which statuses appear as draggable columns.">
|
|
<x-slot:form>
|
|
<form wire:submit="saveBoard" class="grid gap-4 md:grid-cols-6">
|
|
<flux:input wire:model="boardForm.name" label="Name" />
|
|
<flux:input wire:model="boardForm.slug" label="Slug" placeholder="auto-generated" />
|
|
<flux:input wire:model="boardForm.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="boardForm.is_default" class="rounded border-zinc-300"> Default board
|
|
</label>
|
|
<div class="md:col-span-2">
|
|
<flux:textarea wire:model="boardForm.description" label="Description" rows="2" />
|
|
</div>
|
|
<div class="md:col-span-6">
|
|
<label class="mb-2 block text-sm font-medium text-zinc-700 dark:text-zinc-200">Board columns</label>
|
|
<div class="grid gap-2 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
|
|
@foreach ($statuses as $status)
|
|
<label class="flex items-center gap-2 rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-700 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200">
|
|
<input type="checkbox" wire:model="boardForm.status_ids" value="{{ $status->id }}" class="rounded border-zinc-300">
|
|
<span class="inline-flex h-3 w-3 rounded-full" style="background-color: {{ \Modules\Helpdesk\Support\Color::css($status->color) }}"></span>
|
|
<span>{{ $status->name }}</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<div class="md:col-span-6 flex gap-2">
|
|
<flux:button type="submit" variant="primary">Save board</flux:button>
|
|
<flux:button type="button" wire:click="resetBoardForm" variant="ghost">Reset</flux:button>
|
|
</div>
|
|
</form>
|
|
</x-slot:form>
|
|
|
|
<div class="overflow-hidden rounded-2xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
|
|
<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">Board</th>
|
|
<th class="px-4 py-3">Columns</th>
|
|
<th class="px-4 py-3">Default</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 ($boards as $boardItem)
|
|
<tr>
|
|
<td class="px-4 py-3">
|
|
<div class="font-medium text-zinc-900 dark:text-zinc-100">{{ $boardItem->name }}</div>
|
|
<div class="text-xs text-zinc-500">{{ $boardItem->slug }}</div>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex flex-wrap gap-1">
|
|
@foreach ($boardItem->statuses as $status)
|
|
<span class="rounded-full px-2 py-1 text-xs font-semibold text-white" style="background-color: {{ \Modules\Helpdesk\Support\Color::css($status->color) }}">{{ $status->name }}</span>
|
|
@endforeach
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3"><flux:badge color="{{ $boardItem->is_default ? 'emerald' : 'zinc' }}">{{ $boardItem->is_default ? 'Default' : 'No' }}</flux:badge></td>
|
|
<td class="px-4 py-3 text-right">
|
|
<flux:button size="sm" variant="primary" wire:click="editBoard({{ $boardItem->id }})">Edit</flux:button>
|
|
<flux:button size="sm" variant="danger" wire:click="deleteBoard({{ $boardItem->id }})" wire:confirm="Delete this board?">Delete</flux:button>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr><td colspan="4" class="px-4 py-8 text-center text-zinc-500">No Kanban boards yet.</td></tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</x-helpdesk::settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'git')
|
|
<x-helpdesk::settings-section title="Git repositories" description="Configure repositories that can be linked to projects and tickets. Commits can then be stored against tickets for traceability.">
|
|
<x-slot:form>
|
|
<form wire:submit="saveRepository" class="grid gap-4 md:grid-cols-6">
|
|
<flux:select wire:model="repositoryForm.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="repositoryForm.project_id" label="Project">
|
|
<option value="">No project</option>
|
|
@foreach ($projects as $project)
|
|
<option value="{{ $project->id }}">{{ $project->title }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:input wire:model="repositoryForm.name" label="Name" />
|
|
<flux:input wire:model="repositoryForm.provider" label="Provider" placeholder="git, github, gitlab" />
|
|
<flux:input wire:model="repositoryForm.default_branch" label="Default branch" />
|
|
<flux:input wire:model="repositoryForm.webhook_secret" label="Webhook secret" />
|
|
<div class="md:col-span-6">
|
|
<flux:input wire:model="repositoryForm.remote_url" label="Remote URL" placeholder="git@github.com:company/repo.git" />
|
|
</div>
|
|
<div class="md:col-span-6 flex gap-2">
|
|
<flux:button type="submit" variant="primary">Save repository</flux:button>
|
|
<flux:button type="button" wire:click="resetRepositoryForm" variant="ghost">Reset</flux:button>
|
|
</div>
|
|
</form>
|
|
</x-slot:form>
|
|
<x-helpdesk::repository-table :items="$repositories" />
|
|
</x-helpdesk::settings-section>
|
|
@endif
|
|
|
|
|
|
@if ($section === 'workflow')
|
|
<x-helpdesk::settings-section title="Workflow messages" description="Define the professional email messages sent when tickets are raised, updated, replied to and closed.">
|
|
<x-slot:form>
|
|
<form wire:submit="saveWorkflowTemplate" class="grid gap-4 md:grid-cols-6">
|
|
<flux:input wire:model="workflowForm.name" label="Name" />
|
|
<flux:select wire:model="workflowForm.event" label="Event">
|
|
<option value="ticket.created">Ticket raised</option>
|
|
<option value="ticket.updated">Ticket updated</option>
|
|
<option value="ticket.commented">Response added</option>
|
|
<option value="ticket.reply_created">Reply added</option>
|
|
<option value="ticket.closed">Ticket closed</option>
|
|
</flux:select>
|
|
<flux:select wire:model="workflowForm.to_role" label="Send to">
|
|
<option value="requester">Requester</option>
|
|
<option value="agent">Assigned agent</option>
|
|
<option value="actor">User making change</option>
|
|
</flux:select>
|
|
<flux:input wire:model="workflowForm.cc" label="CC" placeholder="Optional comma-separated emails" />
|
|
<flux:input wire:model="workflowForm.bcc" label="BCC" placeholder="Optional comma-separated emails" />
|
|
<label class="flex items-end gap-2 pb-2 text-sm text-zinc-700 dark:text-zinc-200"><input type="checkbox" wire:model="workflowForm.is_active" class="rounded border-zinc-300"> Active</label>
|
|
<div class="md:col-span-6">
|
|
<flux:input wire:model="workflowForm.subject" label="Subject" placeholder="Ticket @{{ ticket.reference }} has been raised" />
|
|
</div>
|
|
<div class="md:col-span-6">
|
|
<flux:textarea wire:model="workflowForm.html_body" label="HTML email body" rows="8" placeholder="Use tokens like @{{ ticket.reference }}, @{{ ticket.subject }}, @{{ ticket.status }}, @{{ requester.name }}, @{{ ticket.url }}" />
|
|
</div>
|
|
<div class="md:col-span-6">
|
|
<flux:textarea wire:model="workflowForm.text_body" label="Plain text body" rows="4" placeholder="Optional fallback text" />
|
|
</div>
|
|
<div class="md:col-span-6 flex gap-2">
|
|
<flux:button type="submit" variant="primary">Save workflow message</flux:button>
|
|
<flux:button type="button" wire:click="resetWorkflowTemplateForm" variant="ghost">Reset</flux:button>
|
|
</div>
|
|
</form>
|
|
</x-slot:form>
|
|
|
|
<div class="overflow-hidden rounded-2xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
|
|
<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">Event</th>
|
|
<th class="px-4 py-3">Recipient</th>
|
|
<th class="px-4 py-3">Active</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 ($workflowTemplates as $template)
|
|
<tr>
|
|
<td class="px-4 py-3 font-medium text-zinc-900 dark:text-zinc-100">{{ $template->name }}</td>
|
|
<td class="px-4 py-3 text-zinc-600 dark:text-zinc-300">{{ $template->event }}</td>
|
|
<td class="px-4 py-3 text-zinc-600 dark:text-zinc-300">{{ $template->to_role }}</td>
|
|
<td class="px-4 py-3"><flux:badge color="{{ $template->is_active ? 'emerald' : 'zinc' }}">{{ $template->is_active ? 'Active' : 'Inactive' }}</flux:badge></td>
|
|
<td class="px-4 py-3 text-right">
|
|
<flux:button size="sm" variant="ghost" wire:click="editWorkflowTemplate({{ $template->id }})">Edit</flux:button>
|
|
<flux:button size="sm" variant="danger" wire:click="deleteWorkflowTemplate({{ $template->id }})" wire:confirm="Delete this workflow message?">Delete</flux:button>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr><td colspan="5" class="px-4 py-8 text-center text-zinc-500">No workflow messages yet.</td></tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</x-helpdesk::settings-section>
|
|
@endif
|
|
|
|
</div>
|