1284 lines
82 KiB
PHP
Executable File
1284 lines
82 KiB
PHP
Executable File
@php
|
|
/**
|
|
* Keep the settings view safe during rolling module upgrades or Livewire navigate
|
|
* hydration where the compiled Blade view may be newer than the loaded component.
|
|
*/
|
|
$statuses = $statuses ?? collect();
|
|
$priorities = $priorities ?? collect();
|
|
$categories = $categories ?? collect();
|
|
$teams = $teams ?? collect();
|
|
$projects = $projects ?? collect();
|
|
$boards = $boards ?? collect();
|
|
$repositories = $repositories ?? collect();
|
|
$workflowTemplates = $workflowTemplates ?? collect();
|
|
$cannedResponses = $cannedResponses ?? collect();
|
|
$slaPolicies = $slaPolicies ?? collect();
|
|
$automationRules = $automationRules ?? collect();
|
|
$checklistTemplates = $checklistTemplates ?? collect();
|
|
$tags = $tags ?? collect();
|
|
$products = $products ?? collect();
|
|
@endphp
|
|
|
|
<div class="hd-helpdesk-surface hd-mobile-ready hd-core-settings space-y-4" dusk="helpdesk-settings-manager">
|
|
<div
|
|
class="hd-settings-hero overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
|
|
<div class="hd-settings-hero-inner bg-gradient-to-br from-slate-950 via-slate-900 to-blue-950 px-5 py-5 text-white">
|
|
<div class="flex flex-col gap-3 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-2xl font-semibold tracking-tight">Support configuration</h1>
|
|
<p class="mt-1 max-w-3xl text-sm text-blue-100/90">Manage lookup data, workflow messages, canned
|
|
responses, SLA policies, automation rules, checklists, tags and product/service linkage.</p>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
<a href="{{ route('helpdesk.index') }}" wire:navigate class="hd-btn hd-btn-blue">
|
|
<i class="fa-solid fa-gauge-high" aria-hidden="true"></i>
|
|
<span>Dashboard</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="hd-settings-section-grid grid gap-2 md:grid-cols-4 xl:grid-cols-7">
|
|
@foreach ([
|
|
'statuses' => ['label' => 'Statuses', 'count' => method_exists($statuses, 'total') ? $statuses->total() : $statuses->count()],
|
|
'priorities' => ['label' => 'Priorities', 'count' => method_exists($priorities, 'total') ? $priorities->total() : $priorities->count()],
|
|
'categories' => ['label' => 'Categories', 'count' => method_exists($categories, 'total') ? $categories->total() : $categories->count()],
|
|
'teams' => ['label' => 'Teams', 'count' => method_exists($teams, 'total') ? $teams->total() : $teams->count()],
|
|
'projects' => ['label' => 'Projects', 'count' => method_exists($projects, 'total') ? $projects->total() : $projects->count()],
|
|
'boards' => ['label' => 'Boards', 'count' => method_exists($boards, 'total') ? $boards->total() : $boards->count()],
|
|
'git' => ['label' => 'Git', 'count' => method_exists($repositories, 'total') ? $repositories->total() : $repositories->count()],
|
|
'workflow' => ['label' => 'Workflow', 'count' => method_exists($workflowTemplates, 'total') ? $workflowTemplates->total() : $workflowTemplates->count()],
|
|
'canned' => ['label' => 'Canned', 'count' => method_exists($cannedResponses, 'total') ? $cannedResponses->total() : $cannedResponses->count()],
|
|
'sla' => ['label' => 'SLA', 'count' => method_exists($slaPolicies, 'total') ? $slaPolicies->total() : $slaPolicies->count()],
|
|
'automation' => ['label' => 'Rules', 'count' => method_exists($automationRules, 'total') ? $automationRules->total() : $automationRules->count()],
|
|
'checklists' => ['label' => 'Checks', 'count' => method_exists($checklistTemplates, 'total') ? $checklistTemplates->total() : $checklistTemplates->count()],
|
|
'tags' => ['label' => 'Tags', 'count' => method_exists($tags, 'total') ? $tags->total() : $tags->count()],
|
|
'products' => ['label' => 'Products', 'count' => method_exists($products, 'total') ? $products->total() : $products->count()],
|
|
] as $key => $item)
|
|
@php
|
|
$sectionPalette = match ($key) {
|
|
'statuses' => ['border' => '#38bdf8', 'bg' => 'rgba(14, 165, 233, .22)', 'active' => '#0284c7'],
|
|
'priorities' => ['border' => '#f87171', 'bg' => 'rgba(239, 68, 68, .22)', 'active' => '#dc2626'],
|
|
'categories' => ['border' => '#818cf8', 'bg' => 'rgba(99, 102, 241, .22)', 'active' => '#4f46e5'],
|
|
'teams' => ['border' => '#60a5fa', 'bg' => 'rgba(37, 99, 235, .22)', 'active' => '#2563eb'],
|
|
'projects' => ['border' => '#22d3ee', 'bg' => 'rgba(8, 145, 178, .22)', 'active' => '#0891b2'],
|
|
'boards' => ['border' => '#fbbf24', 'bg' => 'rgba(245, 158, 11, .24)', 'active' => '#f59e0b'],
|
|
'git' => ['border' => '#94a3b8', 'bg' => 'rgba(71, 85, 105, .28)', 'active' => '#475569'],
|
|
'workflow' => ['border' => '#34d399', 'bg' => 'rgba(5, 150, 105, .22)', 'active' => '#059669'],
|
|
'canned' => ['border' => '#2dd4bf', 'bg' => 'rgba(13, 148, 136, .22)', 'active' => '#0d9488'],
|
|
'sla' => ['border' => '#fb923c', 'bg' => 'rgba(234, 88, 12, .22)', 'active' => '#ea580c'],
|
|
'automation' => ['border' => '#c084fc', 'bg' => 'rgba(147, 51, 234, .22)', 'active' => '#9333ea'],
|
|
'checklists' => ['border' => '#a3e635', 'bg' => 'rgba(101, 163, 13, .22)', 'active' => '#65a30d'],
|
|
'tags' => ['border' => '#f472b6', 'bg' => 'rgba(219, 39, 119, .22)', 'active' => '#db2777'],
|
|
'products' => ['border' => '#a78bfa', 'bg' => 'rgba(124, 58, 237, .22)', 'active' => '#7c3aed'],
|
|
default => ['border' => '#71717a', 'bg' => 'rgba(63, 63, 70, .22)', 'active' => '#3f3f46'],
|
|
};
|
|
|
|
$sectionStyle = '--hd-card-border: ' . $sectionPalette['border'] .
|
|
'; --hd-card-bg: ' . $sectionPalette['bg'] .
|
|
'; --hd-card-active: ' . $sectionPalette['active'] . ';';
|
|
@endphp
|
|
<a href="{{ route('helpdesk.settings', ['section' => $key]) }}"
|
|
dusk="helpdesk-settings-section-{{ $key }}"
|
|
class="hd-settings-section-card {{ $section === $key ? 'is-active' : '' }} rounded-xl border px-3 py-2 text-left shadow-sm transition hover:-translate-y-0.5"
|
|
style="{{ $sectionStyle }}">
|
|
<div class="text-xs font-semibold uppercase tracking-wide opacity-70">{{ $item['label'] }}</div>
|
|
<div class="mt-0.5 text-xl font-semibold">{{ $item['count'] }}</div>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
|
|
@if ($section === 'statuses')
|
|
<x-helpdesk.settings-section title="Statuses"
|
|
description="Control workflow stages and mark which statuses close tickets.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateStatusModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create status
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-statuses-editor"
|
|
wire:model="statusModalOpen"
|
|
class="hd-settings-modal w-full max-w-4xl"
|
|
@close="closeStatusModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--status">
|
|
<flux:heading size="lg">{{ filled($statusForm['id'] ?? null) ? 'Edit status' : 'Create status' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this status without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveStatus" class="grid items-start gap-x-4 gap-y-5 xl:grid-cols-12">
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="statusForm.name" label="Name" />
|
|
</div>
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="statusForm.slug" label="Slug" placeholder="auto-generated" />
|
|
</div>
|
|
<x-helpdesk.colour-select class="xl:col-span-12" model="statusForm.color" label="Badge colour" :options="$colourOptions" />
|
|
<div class="xl:col-span-2">
|
|
<flux:input wire:model="statusForm.sort_order" label="Sort" type="number" min="0" />
|
|
</div>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200 xl:col-span-3">
|
|
<input type="checkbox" wire:model="statusForm.is_closed" class="rounded border-zinc-600 bg-zinc-950 text-blue-600 focus:ring-blue-500">
|
|
Closed status
|
|
</label>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200 xl:col-span-4">
|
|
<input type="checkbox" wire:model="statusForm.is_default"
|
|
class="rounded border-zinc-300 bg-zinc-950 text-blue-600 focus:ring-blue-500">
|
|
Default status for new tickets
|
|
</label>
|
|
|
|
<div class="flex flex-wrap items-center gap-2 xl:col-span-12">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save status
|
|
</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeStatusModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<x-helpdesk.lookup-table :items="$statuses" edit="editStatus" delete="deleteStatus" />
|
|
<x-helpdesk.settings-pagination :paginator="$statuses" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'priorities')
|
|
<x-helpdesk.settings-section title="Priorities" description="Define ticket urgency levels and display order.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreatePriorityModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create priority
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-priorities-editor"
|
|
wire:model="priorityModalOpen"
|
|
class="hd-settings-modal w-full max-w-4xl"
|
|
@close="closePriorityModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--priority">
|
|
<flux:heading size="lg">{{ filled($priorityForm['id'] ?? null) ? 'Edit priority' : 'Create priority' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this priority without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="savePriority" class="grid items-start gap-x-4 gap-y-5 xl:grid-cols-12">
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="priorityForm.name" label="Name" />
|
|
</div>
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="priorityForm.slug" label="Slug" placeholder="auto-generated" />
|
|
</div>
|
|
<x-helpdesk.colour-select class="xl:col-span-12" model="priorityForm.color" label="Badge colour" :options="$colourOptions" />
|
|
<div class="xl:col-span-2">
|
|
<flux:input wire:model="priorityForm.sort_order" label="Sort" type="number" min="0" />
|
|
</div>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200 xl:col-span-4">
|
|
<input type="checkbox" wire:model="priorityForm.is_default"
|
|
class="rounded border-zinc-300 bg-zinc-950 text-blue-600 focus:ring-blue-500">
|
|
Default for new tickets
|
|
</label>
|
|
<div class="flex flex-wrap items-center gap-2 xl:col-span-12">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save priority
|
|
</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closePriorityModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<x-helpdesk.lookup-table :items="$priorities" edit="editPriority" delete="deletePriority" />
|
|
<x-helpdesk.settings-pagination :paginator="$priorities" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'categories')
|
|
<x-helpdesk.settings-section title="Categories"
|
|
description="Group tickets by support area.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateCategoryModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create category
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-categories-editor"
|
|
wire:model="categoryModalOpen"
|
|
class="hd-settings-modal w-full max-w-4xl"
|
|
@close="closeCategoryModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--category">
|
|
<flux:heading size="lg">{{ filled($categoryForm['id'] ?? null) ? 'Edit category' : 'Create category' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this category without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveCategory" class="grid items-start gap-x-4 gap-y-5 xl:grid-cols-12">
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="categoryForm.name" label="Name" />
|
|
</div>
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="categoryForm.slug" label="Slug" placeholder="auto-generated" />
|
|
</div>
|
|
<x-helpdesk.colour-select class="xl:col-span-12" model="categoryForm.color" label="Badge colour" :options="$colourOptions" />
|
|
<div class="xl:col-span-12">
|
|
<flux:textarea wire:model="categoryForm.description" label="Description" rows="3" />
|
|
</div>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200 xl:col-span-4">
|
|
<input type="checkbox" wire:model="categoryForm.is_default"
|
|
class="rounded border-zinc-300 bg-zinc-950 text-blue-600 focus:ring-blue-500">
|
|
Default category for new tickets
|
|
</label>
|
|
<div class="flex flex-wrap items-center gap-2 xl:col-span-12">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save
|
|
category</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeCategoryModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<x-helpdesk.lookup-table :items="$categories" edit="editCategory" delete="deleteCategory" />
|
|
<x-helpdesk.settings-pagination :paginator="$categories" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'teams')
|
|
<x-helpdesk.settings-section title="Teams"
|
|
description="Create support teams, assign users and organise projects.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateTeamModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create team
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<x-helpdesk.team-table :items="$teams" />
|
|
|
|
<flux:modal
|
|
name="helpdesk-team-editor"
|
|
wire:model="teamModalOpen"
|
|
class="hd-settings-modal w-full max-w-5xl"
|
|
@close="closeTeamModal"
|
|
>
|
|
<form wire:submit="saveTeam" class="space-y-5">
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--team flex items-start justify-between gap-4">
|
|
<div>
|
|
<flux:heading size="lg">
|
|
{{ filled($teamForm['id'] ?? null) ? 'Edit team' : 'Create team' }}
|
|
</flux:heading>
|
|
<flux:subheading class="mt-1">
|
|
Configure team details and choose the users who belong to it.
|
|
</flux:subheading>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid gap-4 md:grid-cols-2">
|
|
<flux:input wire:model="teamForm.name" label="Name" />
|
|
<flux:input wire:model="teamForm.slug" label="Slug" placeholder="auto-generated" />
|
|
</div>
|
|
|
|
<x-helpdesk.colour-select model="teamForm.color" label="Badge colour" :options="$colourOptions" />
|
|
|
|
<flux:textarea wire:model="teamForm.description" label="Description" rows="3" />
|
|
|
|
<div class="rounded-xl border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-700 dark:bg-zinc-950/55">
|
|
<div class="mb-3 flex flex-wrap items-center justify-between gap-2">
|
|
<div>
|
|
<div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">Team members</div>
|
|
<div class="mt-0.5 text-xs text-zinc-500">Select one or more users to assign to this team.</div>
|
|
</div>
|
|
<div class="rounded-full bg-blue-500/10 px-2.5 py-1 text-xs font-semibold text-blue-600 dark:text-blue-300">
|
|
{{ count($teamForm['user_ids'] ?? []) }} selected
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid max-h-72 gap-2 overflow-y-auto pr-1 sm:grid-cols-2 xl:grid-cols-3">
|
|
@forelse ($teamUsers as $teamUser)
|
|
<label class="flex cursor-pointer items-center gap-3 rounded-lg border border-zinc-200 bg-white p-3 transition hover:border-blue-500/60 dark:border-zinc-700 dark:bg-zinc-900/70 dark:hover:border-blue-500/60">
|
|
<input
|
|
type="checkbox"
|
|
wire:model="teamForm.user_ids"
|
|
value="{{ $teamUser->id }}"
|
|
class="h-4 w-4 rounded border-zinc-600 bg-zinc-950 text-blue-600 focus:ring-blue-500"
|
|
>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="truncate text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ $teamUser->name }}</div>
|
|
<div class="truncate text-xs text-zinc-500">{{ $teamUser->email }}</div>
|
|
</div>
|
|
@if ($teamUser->helpdesk_admin)
|
|
<span class="rounded-full bg-purple-500/10 px-2 py-0.5 text-[10px] font-bold uppercase text-purple-600 dark:text-purple-300">Admin</span>
|
|
@elseif ($teamUser->helpdesk_agent)
|
|
<span class="rounded-full bg-blue-500/10 px-2 py-0.5 text-[10px] font-bold uppercase text-blue-600 dark:text-blue-300">Agent</span>
|
|
@endif
|
|
</label>
|
|
@empty
|
|
<div class="text-sm text-zinc-500">No users are available.</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
<label class="flex min-h-10 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200">
|
|
<input type="checkbox" wire:model="teamForm.is_default"
|
|
class="rounded border-zinc-300 bg-zinc-950 text-blue-600 focus:ring-blue-500">
|
|
Default team for new tickets
|
|
</label>
|
|
|
|
<div class="flex flex-wrap justify-end gap-2 border-t border-zinc-200 pt-4 dark:border-zinc-700">
|
|
<flux:button type="button" variant="ghost" wire:click="closeTeamModal">
|
|
Cancel
|
|
</flux:button>
|
|
<flux:button type="submit" variant="primary"
|
|
class="!bg-green-600 !text-white hover:!bg-green-700">
|
|
{{ filled($teamForm['id'] ?? null) ? 'Save changes' : 'Create team' }}
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<x-helpdesk.settings-pagination :paginator="$teams" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'projects')
|
|
<x-helpdesk.settings-section title="Projects"
|
|
description="Create projects and optionally assign them to support teams.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateProjectModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create project
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-projects-editor"
|
|
wire:model="projectModalOpen"
|
|
class="hd-settings-modal w-full max-w-5xl"
|
|
@close="closeProjectModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--project">
|
|
<flux:heading size="lg">{{ filled($projectForm['id'] ?? null) ? 'Edit project' : 'Create project' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this project without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveProject" class="grid items-start gap-x-4 gap-y-5 xl:grid-cols-12">
|
|
<div class="xl:col-span-2">
|
|
</div>
|
|
<div class="xl:col-span-2">
|
|
<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>
|
|
</div>
|
|
<div class="xl:col-span-2">
|
|
<flux:input wire:model="projectForm.title" label="Title" />
|
|
</div>
|
|
<div class="xl:col-span-2">
|
|
<flux:input wire:model="projectForm.slug" label="Slug" placeholder="auto-generated" />
|
|
</div>
|
|
<x-helpdesk.colour-select class="xl:col-span-12" model="projectForm.color" label="Badge colour" :options="$colourOptions" />
|
|
<div class="xl:col-span-12">
|
|
<flux:textarea wire:model="projectForm.description" label="Description" rows="3" />
|
|
</div>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200 xl:col-span-4">
|
|
<input type="checkbox" wire:model="projectForm.is_default"
|
|
class="rounded border-zinc-300 bg-zinc-950 text-blue-600 focus:ring-blue-500">
|
|
Default project for new tickets
|
|
</label>
|
|
<div class="flex flex-wrap items-center gap-2 xl:col-span-12">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save project
|
|
</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeProjectModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<x-helpdesk.project-table :items="$projects" />
|
|
<x-helpdesk.settings-pagination :paginator="$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:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateBoardModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create board
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-boards-editor"
|
|
wire:model="boardModalOpen"
|
|
class="hd-settings-modal w-full max-w-6xl"
|
|
@close="closeBoardModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--board">
|
|
<flux:heading size="lg">{{ filled($boardForm['id'] ?? null) ? 'Edit board' : 'Create board' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this board without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveBoard" class="grid items-start gap-x-4 gap-y-5 md:grid-cols-7">
|
|
<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" />
|
|
<flux:select wire:model="boardForm.owner_id" label="Owner">
|
|
<option value="">No owner</option>
|
|
@foreach ($boardOwners as $owner)
|
|
<option value="{{ $owner->id }}">{{ $owner->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 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-full">
|
|
<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-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save board
|
|
</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeBoardModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
|
|
<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">Owner</th>
|
|
<th class="px-4 py-3">Columns</th>
|
|
<th class="px-4 py-3">Tickets</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 text-sm text-zinc-600 dark:text-zinc-300">
|
|
{{ $boardItem->owner?->name ?? 'Unassigned' }}</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 text-zinc-500">{{ $boardItem->tickets_count ?? 0 }}</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 }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">
|
|
Edit</flux:button>
|
|
<flux:button size="sm" variant="danger"
|
|
wire:click="deleteBoard({{ $boardItem->id }})"
|
|
wire:confirm="Delete this board?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">Delete
|
|
</flux:button>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="6" class="px-4 py-8 text-center text-zinc-500">No Kanban boards yet.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<x-helpdesk.settings-pagination :paginator="$boards" />
|
|
</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:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateRepositoryModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create repository
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-git-editor"
|
|
wire:model="repositoryModalOpen"
|
|
class="hd-settings-modal w-full max-w-5xl"
|
|
@close="closeRepositoryModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--git">
|
|
<flux:heading size="lg">{{ filled($repositoryForm['id'] ?? null) ? 'Edit repository' : 'Create repository' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this repository without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveRepository" class="grid items-start gap-x-4 gap-y-5 md:grid-cols-6">
|
|
<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-full">
|
|
<flux:input wire:model="repositoryForm.remote_url" label="Remote URL"
|
|
placeholder="git@github.com:organisation/repo.git" />
|
|
</div>
|
|
<div class="md:col-span-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save
|
|
repository</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeRepositoryModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<x-helpdesk.repository-table :items="$repositories" />
|
|
<x-helpdesk.settings-pagination :paginator="$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:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateWorkflowTemplateModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create workflow message
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-workflow-editor"
|
|
wire:model="workflowModalOpen"
|
|
class="hd-settings-modal w-full max-w-6xl"
|
|
@close="closeWorkflowTemplateModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--workflow">
|
|
<flux:heading size="lg">{{ filled($workflowForm['id'] ?? null) ? 'Edit workflow message' : 'Create workflow message' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this workflow message without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveWorkflowTemplate" class="grid items-start gap-x-4 gap-y-5 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 min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200"><input
|
|
type="checkbox" wire:model="workflowForm.is_active" class="rounded border-zinc-300">
|
|
Active</label>
|
|
<div class="md:col-span-full">
|
|
<flux:input wire:model="workflowForm.subject" label="Subject"
|
|
placeholder="Ticket @{{ ticket.reference }} has been raised" />
|
|
</div>
|
|
<div class="md:col-span-full">
|
|
<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-full">
|
|
<flux:textarea wire:model="workflowForm.text_body" label="Plain text body" rows="4"
|
|
placeholder="Optional fallback text" />
|
|
</div>
|
|
<div class="md:col-span-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save
|
|
workflow message</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeWorkflowTemplateModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
|
|
<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 }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">
|
|
Edit</flux:button>
|
|
<flux:button size="sm" variant="danger"
|
|
wire:click="deleteWorkflowTemplate({{ $template->id }})"
|
|
wire:confirm="Delete this workflow message?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">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-pagination :paginator="$workflowTemplates" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
|
|
@if ($section === 'canned')
|
|
<x-helpdesk.settings-section title="Canned responses"
|
|
description="Save professional reusable replies with macro tokens for ticket, requester, agent, status and URL values.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateCannedResponseModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create canned response
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-canned-editor"
|
|
wire:model="cannedModalOpen"
|
|
class="hd-settings-modal w-full max-w-6xl"
|
|
@close="closeCannedResponseModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--canned">
|
|
<flux:heading size="lg">{{ filled($cannedForm['id'] ?? null) ? 'Edit canned response' : 'Create canned response' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this canned response without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveCannedResponse" class="grid items-start gap-x-4 gap-y-5 md:grid-cols-6">
|
|
<flux:input wire:model="cannedForm.name" label="Name" />
|
|
<flux:input wire:model="cannedForm.slug" label="Slug" placeholder="auto-generated" />
|
|
<flux:input wire:model="cannedForm.sort_order" label="Sort" type="number" min="0" />
|
|
<flux:select wire:model="cannedForm.category_id" label="Category">
|
|
<option value="">Any category</option>
|
|
@foreach ($categories as $category)
|
|
<option value="{{ $category->id }}">{{ $category->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:select wire:model="cannedForm.team_id" label="Team">
|
|
<option value="">Any team</option>
|
|
@foreach ($teams as $team)
|
|
<option value="{{ $team->id }}">{{ $team->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200"><input
|
|
type="checkbox" wire:model="cannedForm.is_active" class="rounded border-zinc-300">
|
|
Active</label>
|
|
<div class="md:col-span-full">
|
|
<flux:input wire:model="cannedForm.subject" label="Optional subject" />
|
|
</div>
|
|
<div class="md:col-span-full">
|
|
<flux:textarea wire:model="cannedForm.body" label="Reply body" rows="6"
|
|
placeholder="Hello @{{ requester.name }}, Your ticket @{{ ticket.reference }} has been reviewed." />
|
|
</div>
|
|
<div class="md:col-span-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save canned
|
|
response</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeCannedResponseModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
@forelse ($cannedResponses as $response)
|
|
<div class="rounded-2xl border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div>
|
|
<h3 class="font-semibold text-zinc-900 dark:text-zinc-100">{{ $response->name }}</h3>
|
|
<p class="text-xs text-zinc-500">{{ $response->category?->name ?? 'Any category' }} ·
|
|
{{ $response->team?->name ?? 'Any team' }}</p>
|
|
</div>
|
|
<flux:badge color="{{ $response->is_active ? 'emerald' : 'zinc' }}">
|
|
{{ $response->is_active ? 'Active' : 'Inactive' }}</flux:badge>
|
|
</div>
|
|
<p class="mt-3 line-clamp-3 text-sm text-zinc-600 dark:text-zinc-300">
|
|
{{ strip_tags($response->body) }}</p>
|
|
<div class="mt-4 flex justify-end gap-2">
|
|
<flux:button size="sm" variant="primary"
|
|
wire:click="editCannedResponse({{ $response->id }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">Edit
|
|
</flux:button>
|
|
<flux:button size="sm" variant="danger"
|
|
wire:click="deleteCannedResponse({{ $response->id }})"
|
|
wire:confirm="Delete this canned response?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">Delete
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div
|
|
class="rounded-2xl border border-dashed border-zinc-300 p-8 text-center text-sm text-zinc-500 dark:border-zinc-700 md:col-span-2 xl:col-span-3">
|
|
No canned responses yet.</div>
|
|
@endforelse
|
|
</div>
|
|
<x-helpdesk.settings-pagination :paginator="$cannedResponses" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'sla')
|
|
<x-helpdesk.settings-section title="SLA policies"
|
|
description="Define first response and resolution targets used for breach indicators, reporting and ticket cards.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateSlaPolicyModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create SLA policy
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-sla-editor"
|
|
wire:model="slaModalOpen"
|
|
class="hd-settings-modal w-full max-w-5xl"
|
|
@close="closeSlaPolicyModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--sla">
|
|
<flux:heading size="lg">{{ filled($slaForm['id'] ?? null) ? 'Edit SLA policy' : 'Create SLA policy' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this SLA policy without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveSlaPolicy" class="grid items-start gap-x-4 gap-y-5 md:grid-cols-6">
|
|
<flux:input wire:model="slaForm.name" label="Name" />
|
|
<flux:input wire:model="slaForm.slug" label="Slug" placeholder="auto-generated" />
|
|
<flux:select wire:model="slaForm.priority_id" label="Priority">
|
|
<option value="">Any priority</option>
|
|
@foreach ($priorities as $priority)
|
|
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:select wire:model="slaForm.category_id" label="Category">
|
|
<option value="">Any category</option>
|
|
@foreach ($categories as $category)
|
|
<option value="{{ $category->id }}">{{ $category->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:input wire:model="slaForm.first_response_minutes" label="First response minutes"
|
|
type="number" min="1" />
|
|
<flux:input wire:model="slaForm.resolution_minutes" label="Resolution minutes" type="number"
|
|
min="1" />
|
|
<label class="flex items-center gap-2 text-sm text-zinc-700 dark:text-zinc-200"><input
|
|
type="checkbox" wire:model="slaForm.is_default" class="rounded border-zinc-300">
|
|
Default</label>
|
|
<label class="flex items-center gap-2 text-sm text-zinc-700 dark:text-zinc-200"><input
|
|
type="checkbox" wire:model="slaForm.business_hours_only" class="rounded border-zinc-300">
|
|
Business hours</label>
|
|
<label class="flex items-center gap-2 text-sm text-zinc-700 dark:text-zinc-200"><input
|
|
type="checkbox" wire:model="slaForm.is_active" class="rounded border-zinc-300">
|
|
Active</label>
|
|
<div class="md:col-span-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save SLA
|
|
policy</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeSlaPolicyModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<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 text-zinc-500 dark:bg-zinc-800">
|
|
<tr>
|
|
<th class="px-4 py-3">Policy</th>
|
|
<th class="px-4 py-3">Targets</th>
|
|
<th class="px-4 py-3">Applies to</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 ($slaPolicies as $policy)
|
|
<tr>
|
|
<td class="px-4 py-3 font-medium">{{ $policy->name }} @if ($policy->is_default)
|
|
<flux:badge color="blue">Default</flux:badge>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3">{{ $policy->first_response_minutes }}m response ·
|
|
{{ $policy->resolution_minutes }}m close</td>
|
|
<td class="px-4 py-3">{{ $policy->priority?->name ?? 'Any priority' }} ·
|
|
{{ $policy->category?->name ?? 'Any category' }}</td>
|
|
<td class="px-4 py-3 text-right">
|
|
<flux:button size="sm" variant="primary"
|
|
wire:click="editSlaPolicy({{ $policy->id }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">
|
|
Edit</flux:button>
|
|
<flux:button size="sm" variant="danger"
|
|
wire:click="deleteSlaPolicy({{ $policy->id }})"
|
|
wire:confirm="Delete this SLA policy?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">Delete
|
|
</flux:button>
|
|
</td>
|
|
</tr>@empty<tr>
|
|
<td colspan="6" class="px-4 py-8 text-center text-zinc-500">No SLA policies yet.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<x-helpdesk.settings-pagination :paginator="$slaPolicies" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'automation')
|
|
<x-helpdesk.settings-section title="Email automation rules"
|
|
description="Match incoming support messages and automatically assign priority, category, team, project, agent and status.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateAutomationRuleModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create automation rule
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-automation-editor"
|
|
wire:model="automationModalOpen"
|
|
class="hd-settings-modal w-full max-w-6xl"
|
|
@close="closeAutomationRuleModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--automation">
|
|
<flux:heading size="lg">{{ filled($automationForm['id'] ?? null) ? 'Edit automation rule' : 'Create automation rule' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this automation rule without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveAutomationRule" class="grid items-start gap-x-4 gap-y-5 md:grid-cols-6">
|
|
<flux:input wire:model="automationForm.name" label="Name" />
|
|
<flux:input wire:model="automationForm.slug" label="Slug" placeholder="auto-generated" />
|
|
<flux:input wire:model="automationForm.sort_order" label="Sort" type="number"
|
|
min="0" />
|
|
<flux:input wire:model="automationForm.match_sender" label="Sender contains" />
|
|
<flux:input wire:model="automationForm.match_subject" label="Subject contains" />
|
|
<flux:input wire:model="automationForm.match_mailbox" label="Mailbox contains" />
|
|
<flux:select wire:model="automationForm.set_priority_id" label="Set priority">
|
|
<option value="">No change</option>
|
|
@foreach ($priorities as $priority)
|
|
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:select wire:model="automationForm.set_category_id" label="Set category">
|
|
<option value="">No change</option>
|
|
@foreach ($categories as $category)
|
|
<option value="{{ $category->id }}">{{ $category->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:select wire:model="automationForm.set_team_id" label="Set team">
|
|
<option value="">No change</option>
|
|
@foreach ($teams as $team)
|
|
<option value="{{ $team->id }}">{{ $team->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:select wire:model="automationForm.set_project_id" label="Set project">
|
|
<option value="">No change</option>
|
|
@foreach ($projects as $project)
|
|
<option value="{{ $project->id }}">{{ $project->title }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:select wire:model="automationForm.set_status_id" label="Set status">
|
|
<option value="">No change</option>
|
|
@foreach ($statuses as $status)
|
|
<option value="{{ $status->id }}">{{ $status->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<label class="flex min-h-9 items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-xs font-semibold text-zinc-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/60 dark:text-zinc-200"><input
|
|
type="checkbox" wire:model="automationForm.is_active" class="rounded border-zinc-300">
|
|
Active</label>
|
|
<div class="md:col-span-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save
|
|
automation rule</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeAutomationRuleModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<div class="grid gap-3 md:grid-cols-2">
|
|
@forelse ($automationRules as $rule)
|
|
<div class="rounded-2xl border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
|
<h3 class="font-semibold">{{ $rule->name }}</h3>
|
|
<p class="mt-1 text-sm text-zinc-500">Sender: {{ $rule->match_sender ?: 'Any' }} · Subject:
|
|
{{ $rule->match_subject ?: 'Any' }}</p>
|
|
<div class="mt-4 flex justify-end gap-2">
|
|
<flux:button size="sm" variant="primary"
|
|
wire:click="editAutomationRule({{ $rule->id }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">Edit
|
|
</flux:button>
|
|
<flux:button size="sm" variant="danger"
|
|
wire:click="deleteAutomationRule({{ $rule->id }})"
|
|
wire:confirm="Delete this rule?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">Delete
|
|
</flux:button>
|
|
</div>
|
|
</div>@empty<div
|
|
class="rounded-2xl border border-dashed border-zinc-300 p-8 text-center text-sm text-zinc-500 dark:border-zinc-700 md:col-span-2">
|
|
No automation rules yet.</div>
|
|
@endforelse
|
|
</div>
|
|
<x-helpdesk.settings-pagination :paginator="$automationRules" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'checklists')
|
|
<x-helpdesk.settings-section title="Checklist templates"
|
|
description="Apply repeatable checklist items by category or project and show completion progress on tickets and Kanban cards.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateChecklistTemplateModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create checklist
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-checklists-editor"
|
|
wire:model="checklistModalOpen"
|
|
class="hd-settings-modal w-full max-w-5xl"
|
|
@close="closeChecklistTemplateModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--checklist">
|
|
<flux:heading size="lg">{{ filled($checklistTemplateForm['id'] ?? null) ? 'Edit checklist template' : 'Create checklist' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this checklist template without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveChecklistTemplate" class="grid items-start gap-x-4 gap-y-5 md:grid-cols-6">
|
|
<flux:input wire:model="checklistTemplateForm.name" label="Name" />
|
|
<flux:input wire:model="checklistTemplateForm.slug" label="Slug" />
|
|
<flux:select wire:model="checklistTemplateForm.category_id" label="Category">
|
|
<option value="">Any category</option>
|
|
@foreach ($categories as $category)
|
|
<option value="{{ $category->id }}">{{ $category->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:select wire:model="checklistTemplateForm.project_id" label="Project">
|
|
<option value="">Any project</option>
|
|
@foreach ($projects as $project)
|
|
<option value="{{ $project->id }}">{{ $project->title }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<label class="flex items-end gap-2 pb-2 text-sm"><input type="checkbox"
|
|
wire:model="checklistTemplateForm.is_active" class="rounded border-zinc-300">
|
|
Active</label>
|
|
<div class="md:col-span-full">
|
|
<flux:textarea wire:model="checklistTemplateForm.items_text"
|
|
label="Checklist items, one per line" rows="6" />
|
|
</div>
|
|
<div class="md:col-span-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save
|
|
checklist</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeChecklistTemplateModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
@forelse ($checklistTemplates as $template)
|
|
<div class="rounded-2xl border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
|
<h3 class="font-semibold">{{ $template->name }}</h3>
|
|
<p class="mt-1 text-sm text-zinc-500">{{ count($template->items ?? []) }} items</p>
|
|
<div class="mt-4 flex justify-end gap-2">
|
|
<flux:button size="sm" variant="primary"
|
|
wire:click="editChecklistTemplate({{ $template->id }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">Edit
|
|
</flux:button>
|
|
<flux:button size="sm" variant="danger"
|
|
wire:click="deleteChecklistTemplate({{ $template->id }})"
|
|
wire:confirm="Delete this checklist?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">Delete
|
|
</flux:button>
|
|
</div>
|
|
</div>@empty<div
|
|
class="rounded-2xl border border-dashed border-zinc-300 p-8 text-center text-sm text-zinc-500 dark:border-zinc-700 md:col-span-2 xl:col-span-3">
|
|
No checklist templates yet.</div>
|
|
@endforelse
|
|
</div>
|
|
<x-helpdesk.settings-pagination :paginator="$checklistTemplates" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'tags')
|
|
<x-helpdesk.settings-section title="Ticket tags"
|
|
description="Maintain visible ticket labels for search, triage and Kanban card context.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateTagModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create tag
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-tags-editor"
|
|
wire:model="tagModalOpen"
|
|
class="hd-settings-modal w-full max-w-4xl"
|
|
@close="closeTagModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--tag">
|
|
<flux:heading size="lg">{{ filled($tagForm['id'] ?? null) ? 'Edit tag' : 'Create tag' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this tag without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveTag" class="grid items-start gap-x-4 gap-y-5 xl:grid-cols-12">
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="tagForm.name" label="Name" />
|
|
</div>
|
|
<div class="xl:col-span-4">
|
|
<flux:input wire:model="tagForm.slug" label="Slug" />
|
|
</div>
|
|
<x-helpdesk.colour-select class="xl:col-span-12" model="tagForm.color" label="Colour" :options="$colourOptions" />
|
|
<div class="flex flex-wrap items-center gap-2 xl:col-span-12">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save tag
|
|
</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeTagModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<div class="flex flex-wrap gap-3">
|
|
@forelse ($tags as $tag)
|
|
<div
|
|
class="rounded-2xl border border-zinc-200 bg-white px-4 py-3 dark:border-zinc-700 dark:bg-zinc-900">
|
|
<span class="rounded-full px-2 py-1 text-xs font-semibold text-white"
|
|
style="background-color: {{ \Modules\Helpdesk\Support\Color::css($tag->color) }}">{{ $tag->name }}</span>
|
|
<div class="mt-3 flex gap-2">
|
|
<flux:button size="sm" variant="primary" wire:click="editTag({{ $tag->id }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">Edit
|
|
</flux:button>
|
|
<flux:button size="sm" variant="danger" wire:click="deleteTag({{ $tag->id }})"
|
|
wire:confirm="Delete this tag?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">Delete
|
|
</flux:button>
|
|
</div>
|
|
</div>@empty<div
|
|
class="rounded-2xl border border-dashed border-zinc-300 p-8 text-center text-sm text-zinc-500 dark:border-zinc-700">
|
|
No tags yet.</div>
|
|
@endforelse
|
|
</div>
|
|
<x-helpdesk.settings-pagination :paginator="$tags" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
@if ($section === 'products')
|
|
<x-helpdesk.settings-section title="Products and services"
|
|
description="Link tickets to products, services, releases or builds and report support cost/time by product.">
|
|
<x-slot:actions>
|
|
<flux:button
|
|
size="sm"
|
|
type="button"
|
|
wire:click="openCreateProductModal"
|
|
variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60"
|
|
>
|
|
<i class="fa-solid fa-plus mr-1" aria-hidden="true"></i>
|
|
Create product
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
|
|
<flux:modal
|
|
name="helpdesk-products-editor"
|
|
wire:model="productModalOpen"
|
|
class="hd-settings-modal w-full max-w-5xl"
|
|
@close="closeProductModal"
|
|
>
|
|
<div class="hd-settings-modal-header hd-settings-modal-header--product">
|
|
<flux:heading size="lg">{{ filled($productForm['id'] ?? null) ? 'Edit product/service' : 'Create product' }}</flux:heading>
|
|
<flux:subheading class="mt-1">Create or edit this product/service without leaving the settings list.</flux:subheading>
|
|
</div>
|
|
<form wire:submit="saveProduct" class="grid items-start gap-x-4 gap-y-5 md:grid-cols-6">
|
|
<flux:input wire:model="productForm.name" label="Name" />
|
|
<flux:input wire:model="productForm.slug" label="Slug" />
|
|
<flux:input wire:model="productForm.type" label="Type" />
|
|
<flux:input wire:model="productForm.version" label="Version" />
|
|
<flux:input wire:model="productForm.release_channel" label="Release/build" /><label
|
|
class="flex items-end gap-2 pb-2 text-sm"><input type="checkbox"
|
|
wire:model="productForm.is_active" class="rounded border-zinc-300"> Active</label>
|
|
<div class="md:col-span-full">
|
|
<flux:textarea wire:model="productForm.description" label="Description" rows="3" />
|
|
</div>
|
|
<div class="md:col-span-full flex gap-2">
|
|
<flux:button size="sm" type="submit" variant="primary"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-green-600 !text-white hover:!bg-green-700 focus:!ring-green-400/60">Save
|
|
product/service</flux:button>
|
|
<flux:button size="sm" type="button" wire:click="closeProductModal" variant="ghost"
|
|
class="!h-9 !rounded-lg !px-3 !text-xs !font-semibold !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60">Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
@forelse ($products as $product)
|
|
<div class="rounded-2xl border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
|
<h3 class="font-semibold">{{ $product->name }}</h3>
|
|
<p class="mt-1 text-sm text-zinc-500">{{ ucfirst($product->type) }} ·
|
|
{{ $product->version ?: 'No version' }} · {{ $product->tickets_count }} tickets</p>
|
|
<div class="mt-4 flex justify-end gap-2">
|
|
<flux:button size="sm" variant="primary"
|
|
wire:click="editProduct({{ $product->id }})"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60">Edit
|
|
</flux:button>
|
|
<flux:button size="sm" variant="danger"
|
|
wire:click="deleteProduct({{ $product->id }})"
|
|
wire:confirm="Delete this product/service?"
|
|
class="!h-8 !rounded-lg !px-2.5 !text-xs !font-semibold !bg-red-600 !text-white hover:!bg-red-700 focus:!ring-red-400/60">Delete
|
|
</flux:button>
|
|
</div>
|
|
</div>@empty<div
|
|
class="rounded-2xl border border-dashed border-zinc-300 p-8 text-center text-sm text-zinc-500 dark:border-zinc-700 md:col-span-2 xl:col-span-3">
|
|
No products or services yet.</div>
|
|
@endforelse
|
|
</div>
|
|
<x-helpdesk.settings-pagination :paginator="$products" />
|
|
</x-helpdesk.settings-section>
|
|
@endif
|
|
|
|
</div>
|