structure updates
This commit is contained in:
@@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use Modules\Helpdesk\Models\AutomationRule;
|
||||
use Modules\Helpdesk\Models\CannedResponse;
|
||||
use Modules\Helpdesk\Models\Category;
|
||||
@@ -30,10 +31,26 @@ use Modules\Helpdesk\Support\Color;
|
||||
|
||||
final class SettingsManager extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $section = 'statuses';
|
||||
|
||||
public bool $teamModalOpen = false;
|
||||
|
||||
public bool $statusModalOpen = false;
|
||||
public bool $priorityModalOpen = false;
|
||||
public bool $categoryModalOpen = false;
|
||||
public bool $projectModalOpen = false;
|
||||
public bool $boardModalOpen = false;
|
||||
public bool $repositoryModalOpen = false;
|
||||
public bool $workflowModalOpen = false;
|
||||
public bool $cannedModalOpen = false;
|
||||
public bool $slaModalOpen = false;
|
||||
public bool $automationModalOpen = false;
|
||||
public bool $checklistModalOpen = false;
|
||||
public bool $tagModalOpen = false;
|
||||
public bool $productModalOpen = false;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
|
||||
|
||||
@@ -141,6 +158,7 @@ final class SettingsManager extends Component
|
||||
|
||||
$this->persistSettingModel(Status::class, $this->statusForm['id'], $data);
|
||||
$this->resetStatusForm();
|
||||
$this->statusModalOpen = false;
|
||||
session()->flash('success', 'Status saved.');
|
||||
}
|
||||
|
||||
@@ -149,6 +167,7 @@ final class SettingsManager extends Component
|
||||
$this->statusForm = Status::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order', 'is_closed', 'is_default']);
|
||||
$this->statusForm['color'] = Color::normalise($this->statusForm['color'] ?? 'gray');
|
||||
$this->section = 'statuses';
|
||||
$this->statusModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteStatus(int $id): void
|
||||
@@ -157,6 +176,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Status deleted.');
|
||||
}
|
||||
|
||||
public function openCreateStatusModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetStatusForm();
|
||||
$this->section = 'statuses';
|
||||
$this->statusModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeStatusModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetStatusForm();
|
||||
$this->statusModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetStatusForm(): void
|
||||
{
|
||||
$this->statusForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false];
|
||||
@@ -180,6 +214,7 @@ final class SettingsManager extends Component
|
||||
}
|
||||
$this->persistSettingModel(Priority::class, $this->priorityForm['id'], $data);
|
||||
$this->resetPriorityForm();
|
||||
$this->priorityModalOpen = false;
|
||||
session()->flash('success', 'Priority saved.');
|
||||
}
|
||||
|
||||
@@ -188,6 +223,7 @@ final class SettingsManager extends Component
|
||||
$this->priorityForm = Priority::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'sort_order', 'is_default']);
|
||||
$this->priorityForm['color'] = Color::normalise($this->priorityForm['color'] ?? 'gray');
|
||||
$this->section = 'priorities';
|
||||
$this->priorityModalOpen = true;
|
||||
}
|
||||
|
||||
public function deletePriority(int $id): void
|
||||
@@ -196,6 +232,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Priority deleted.');
|
||||
}
|
||||
|
||||
public function openCreatePriorityModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetPriorityForm();
|
||||
$this->section = 'priorities';
|
||||
$this->priorityModalOpen = true;
|
||||
}
|
||||
|
||||
public function closePriorityModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetPriorityForm();
|
||||
$this->priorityModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetPriorityForm(): void
|
||||
{
|
||||
$this->priorityForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_default' => false];
|
||||
@@ -221,6 +272,7 @@ final class SettingsManager extends Component
|
||||
}
|
||||
$this->persistSettingModel(Category::class, $this->categoryForm['id'], $data);
|
||||
$this->resetCategoryForm();
|
||||
$this->categoryModalOpen = false;
|
||||
session()->flash('success', 'Category saved.');
|
||||
}
|
||||
|
||||
@@ -229,6 +281,7 @@ final class SettingsManager extends Component
|
||||
$this->categoryForm = Category::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'description', 'is_default']);
|
||||
$this->categoryForm['color'] = Color::normalise($this->categoryForm['color'] ?? 'blue');
|
||||
$this->section = 'categories';
|
||||
$this->categoryModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteCategory(int $id): void
|
||||
@@ -237,6 +290,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Category deleted.');
|
||||
}
|
||||
|
||||
public function openCreateCategoryModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetCategoryForm();
|
||||
$this->section = 'categories';
|
||||
$this->categoryModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeCategoryModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetCategoryForm();
|
||||
$this->categoryModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetCategoryForm(): void
|
||||
{
|
||||
$this->categoryForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false];
|
||||
@@ -401,6 +469,7 @@ final class SettingsManager extends Component
|
||||
}
|
||||
$this->persistSettingModel(Project::class, $this->projectForm['id'], $data);
|
||||
$this->resetProjectForm();
|
||||
$this->projectModalOpen = false;
|
||||
session()->flash('success', 'Project saved.');
|
||||
}
|
||||
|
||||
@@ -409,6 +478,7 @@ final class SettingsManager extends Component
|
||||
$this->projectForm = Project::query()->findOrFail($id)->only(['id', 'team_id', 'title', 'slug', 'color', 'description', 'is_default', 'archived_at']);
|
||||
$this->projectForm['color'] = Color::normalise($this->projectForm['color'] ?? 'blue');
|
||||
$this->section = 'projects';
|
||||
$this->projectModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteProject(int $id): void
|
||||
@@ -417,6 +487,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Project deleted.');
|
||||
}
|
||||
|
||||
public function openCreateProjectModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetProjectForm();
|
||||
$this->section = 'projects';
|
||||
$this->projectModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeProjectModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetProjectForm();
|
||||
$this->projectModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetProjectForm(): void
|
||||
{
|
||||
$this->projectForm = ['id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null];
|
||||
@@ -456,6 +541,7 @@ final class SettingsManager extends Component
|
||||
|
||||
$board->statuses()->sync($sync);
|
||||
$this->resetBoardForm();
|
||||
$this->boardModalOpen = false;
|
||||
$this->section = 'boards';
|
||||
session()->flash('success', 'Kanban board saved.');
|
||||
}
|
||||
@@ -476,6 +562,7 @@ final class SettingsManager extends Component
|
||||
];
|
||||
|
||||
$this->section = 'boards';
|
||||
$this->boardModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteBoard(int $id): void
|
||||
@@ -484,6 +571,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Kanban board deleted.');
|
||||
}
|
||||
|
||||
public function openCreateBoardModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetBoardForm();
|
||||
$this->section = 'boards';
|
||||
$this->boardModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeBoardModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetBoardForm();
|
||||
$this->boardModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetBoardForm(): void
|
||||
{
|
||||
$this->boardForm = ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, 'is_default' => false, 'sort_order' => 0, 'status_ids' => []];
|
||||
@@ -506,6 +608,7 @@ final class SettingsManager extends Component
|
||||
|
||||
$this->persistSettingModel(GitRepository::class, $this->repositoryForm['id'], $data);
|
||||
$this->resetRepositoryForm();
|
||||
$this->repositoryModalOpen = false;
|
||||
session()->flash('success', 'Git repository saved.');
|
||||
}
|
||||
|
||||
@@ -513,6 +616,7 @@ final class SettingsManager extends Component
|
||||
{
|
||||
$this->repositoryForm = GitRepository::query()->findOrFail($id)->only(['id', 'project_id', 'name', 'provider', 'remote_url', 'default_branch', 'webhook_secret']);
|
||||
$this->section = 'git';
|
||||
$this->repositoryModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteRepository(int $id): void
|
||||
@@ -521,6 +625,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Git repository deleted.');
|
||||
}
|
||||
|
||||
public function openCreateRepositoryModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetRepositoryForm();
|
||||
$this->section = 'git';
|
||||
$this->repositoryModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeRepositoryModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetRepositoryForm();
|
||||
$this->repositoryModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetRepositoryForm(): void
|
||||
{
|
||||
$this->repositoryForm = ['id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => ''];
|
||||
@@ -544,6 +663,7 @@ final class SettingsManager extends Component
|
||||
|
||||
$this->persistSettingModel(WorkflowTemplate::class, $this->workflowForm['id'], $data);
|
||||
$this->resetWorkflowTemplateForm();
|
||||
$this->workflowModalOpen = false;
|
||||
session()->flash('success', 'Workflow message saved.');
|
||||
}
|
||||
|
||||
@@ -551,6 +671,7 @@ final class SettingsManager extends Component
|
||||
{
|
||||
$this->workflowForm = WorkflowTemplate::query()->findOrFail($id)->only(['id', 'name', 'event', 'subject', 'html_body', 'text_body', 'to_role', 'cc', 'bcc', 'is_active']);
|
||||
$this->section = 'workflow';
|
||||
$this->workflowModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteWorkflowTemplate(int $id): void
|
||||
@@ -559,6 +680,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Workflow message deleted.');
|
||||
}
|
||||
|
||||
public function openCreateWorkflowTemplateModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetWorkflowTemplateForm();
|
||||
$this->section = 'workflow';
|
||||
$this->workflowModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeWorkflowTemplateModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetWorkflowTemplateForm();
|
||||
$this->workflowModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetWorkflowTemplateForm(): void
|
||||
{
|
||||
$this->workflowForm = ['id' => null, 'name' => '', 'event' => 'ticket.created', 'subject' => '', 'html_body' => '', 'text_body' => '', 'to_role' => 'requester', 'cc' => '', 'bcc' => '', 'is_active' => true];
|
||||
@@ -582,6 +718,7 @@ final class SettingsManager extends Component
|
||||
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
||||
$this->persistSettingModel(CannedResponse::class, $this->cannedForm['id'], $data);
|
||||
$this->resetCannedResponseForm();
|
||||
$this->cannedModalOpen = false;
|
||||
session()->flash('success', 'Canned response saved.');
|
||||
}
|
||||
|
||||
@@ -589,6 +726,7 @@ final class SettingsManager extends Component
|
||||
{
|
||||
$this->cannedForm = CannedResponse::query()->findOrFail($id)->only(['id', 'category_id', 'team_id', 'name', 'slug', 'visibility', 'subject', 'body', 'is_active', 'sort_order']);
|
||||
$this->section = 'canned';
|
||||
$this->cannedModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteCannedResponse(int $id): void
|
||||
@@ -597,6 +735,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Canned response deleted.');
|
||||
}
|
||||
|
||||
public function openCreateCannedResponseModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetCannedResponseForm();
|
||||
$this->section = 'canned';
|
||||
$this->cannedModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeCannedResponseModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetCannedResponseForm();
|
||||
$this->cannedModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetCannedResponseForm(): void
|
||||
{
|
||||
$this->cannedForm = ['id' => null, 'category_id' => null, 'team_id' => null, 'name' => '', 'slug' => '', 'visibility' => 'agents', 'subject' => '', 'body' => '', 'is_active' => true, 'sort_order' => 0];
|
||||
@@ -623,6 +776,7 @@ final class SettingsManager extends Component
|
||||
}
|
||||
$this->persistSettingModel(SlaPolicy::class, $this->slaForm['id'], $data);
|
||||
$this->resetSlaPolicyForm();
|
||||
$this->slaModalOpen = false;
|
||||
session()->flash('success', 'SLA policy saved.');
|
||||
}
|
||||
|
||||
@@ -630,6 +784,7 @@ final class SettingsManager extends Component
|
||||
{
|
||||
$this->slaForm = SlaPolicy::query()->findOrFail($id)->only(['id', 'priority_id', 'category_id', 'name', 'slug', 'first_response_minutes', 'resolution_minutes', 'business_hours_only', 'is_default', 'is_active']);
|
||||
$this->section = 'sla';
|
||||
$this->slaModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteSlaPolicy(int $id): void
|
||||
@@ -638,6 +793,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'SLA policy deleted.');
|
||||
}
|
||||
|
||||
public function openCreateSlaPolicyModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetSlaPolicyForm();
|
||||
$this->section = 'sla';
|
||||
$this->slaModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeSlaPolicyModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetSlaPolicyForm();
|
||||
$this->slaModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetSlaPolicyForm(): void
|
||||
{
|
||||
$this->slaForm = ['id' => null, 'priority_id' => null, 'category_id' => null, 'name' => '', 'slug' => '', 'first_response_minutes' => 240, 'resolution_minutes' => 2880, 'business_hours_only' => false, 'is_default' => false, 'is_active' => true];
|
||||
@@ -666,6 +836,7 @@ final class SettingsManager extends Component
|
||||
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
||||
$this->persistSettingModel(AutomationRule::class, $this->automationForm['id'], $data);
|
||||
$this->resetAutomationRuleForm();
|
||||
$this->automationModalOpen = false;
|
||||
session()->flash('success', 'Automation rule saved.');
|
||||
}
|
||||
|
||||
@@ -673,6 +844,7 @@ final class SettingsManager extends Component
|
||||
{
|
||||
$this->automationForm = AutomationRule::query()->findOrFail($id)->only(['id', 'name', 'slug', 'match_sender', 'match_subject', 'match_mailbox', 'match_category_id', 'set_priority_id', 'set_category_id', 'set_team_id', 'set_project_id', 'set_status_id', 'set_agent_id', 'is_active', 'sort_order']);
|
||||
$this->section = 'automation';
|
||||
$this->automationModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteAutomationRule(int $id): void
|
||||
@@ -681,6 +853,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Automation rule deleted.');
|
||||
}
|
||||
|
||||
public function openCreateAutomationRuleModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetAutomationRuleForm();
|
||||
$this->section = 'automation';
|
||||
$this->automationModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeAutomationRuleModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetAutomationRuleForm();
|
||||
$this->automationModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetAutomationRuleForm(): void
|
||||
{
|
||||
$this->automationForm = ['id' => null, 'name' => '', 'slug' => '', 'match_sender' => '', 'match_subject' => '', 'match_mailbox' => '', 'match_category_id' => null, 'set_priority_id' => null, 'set_category_id' => null, 'set_team_id' => null, 'set_project_id' => null, 'set_status_id' => null, 'set_agent_id' => null, 'is_active' => true, 'sort_order' => 0];
|
||||
@@ -702,6 +889,7 @@ final class SettingsManager extends Component
|
||||
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
||||
$this->persistSettingModel(Product::class, $this->productForm['id'], $data);
|
||||
$this->resetProductForm();
|
||||
$this->productModalOpen = false;
|
||||
session()->flash('success', 'Product/service saved.');
|
||||
}
|
||||
|
||||
@@ -709,6 +897,7 @@ final class SettingsManager extends Component
|
||||
{
|
||||
$this->productForm = Product::query()->findOrFail($id)->only(['id', 'name', 'slug', 'type', 'version', 'release_channel', 'description', 'is_active']);
|
||||
$this->section = 'products';
|
||||
$this->productModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteProduct(int $id): void
|
||||
@@ -717,6 +906,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Product/service deleted.');
|
||||
}
|
||||
|
||||
public function openCreateProductModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetProductForm();
|
||||
$this->section = 'products';
|
||||
$this->productModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeProductModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetProductForm();
|
||||
$this->productModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetProductForm(): void
|
||||
{
|
||||
$this->productForm = ['id' => null, 'name' => '', 'slug' => '', 'type' => 'service', 'version' => '', 'release_channel' => '', 'description' => '', 'is_active' => true];
|
||||
@@ -734,6 +938,7 @@ final class SettingsManager extends Component
|
||||
$data['slug'] = $this->slug($data['slug'] ?: $data['name']);
|
||||
$this->persistSettingModel(Tag::class, $this->tagForm['id'], $data);
|
||||
$this->resetTagForm();
|
||||
$this->tagModalOpen = false;
|
||||
session()->flash('success', 'Tag saved.');
|
||||
}
|
||||
|
||||
@@ -742,6 +947,7 @@ final class SettingsManager extends Component
|
||||
$this->tagForm = Tag::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color']);
|
||||
$this->tagForm['color'] = Color::normalise($this->tagForm['color'] ?? 'gray');
|
||||
$this->section = 'tags';
|
||||
$this->tagModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteTag(int $id): void
|
||||
@@ -750,6 +956,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Tag deleted.');
|
||||
}
|
||||
|
||||
public function openCreateTagModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetTagForm();
|
||||
$this->section = 'tags';
|
||||
$this->tagModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeTagModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetTagForm();
|
||||
$this->tagModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetTagForm(): void
|
||||
{
|
||||
$this->tagForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray'];
|
||||
@@ -773,6 +994,7 @@ final class SettingsManager extends Component
|
||||
$data['items'] = collect(preg_split('/\r\n|\r|\n/', $itemsText) ?: [])->map(fn ($line): string => trim($line))->filter()->values()->all();
|
||||
$this->persistSettingModel(ChecklistTemplate::class, $this->checklistTemplateForm['id'], $data);
|
||||
$this->resetChecklistTemplateForm();
|
||||
$this->checklistModalOpen = false;
|
||||
session()->flash('success', 'Checklist template saved.');
|
||||
}
|
||||
|
||||
@@ -783,6 +1005,7 @@ final class SettingsManager extends Component
|
||||
'items_text' => collect($template->items ?? [])->map(fn ($item): string => is_array($item) ? (string) ($item['title'] ?? '') : (string) $item)->implode("\n"),
|
||||
];
|
||||
$this->section = 'checklists';
|
||||
$this->checklistModalOpen = true;
|
||||
}
|
||||
|
||||
public function deleteChecklistTemplate(int $id): void
|
||||
@@ -791,6 +1014,21 @@ final class SettingsManager extends Component
|
||||
session()->flash('success', 'Checklist template deleted.');
|
||||
}
|
||||
|
||||
public function openCreateChecklistTemplateModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetChecklistTemplateForm();
|
||||
$this->section = 'checklists';
|
||||
$this->checklistModalOpen = true;
|
||||
}
|
||||
|
||||
public function closeChecklistTemplateModal(): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetChecklistTemplateForm();
|
||||
$this->checklistModalOpen = false;
|
||||
}
|
||||
|
||||
public function resetChecklistTemplateForm(): void
|
||||
{
|
||||
$this->checklistTemplateForm = ['id' => null, 'category_id' => null, 'project_id' => null, 'name' => '', 'slug' => '', 'items_text' => '', 'is_active' => true];
|
||||
@@ -810,15 +1048,45 @@ final class SettingsManager extends Component
|
||||
'boardOwners' => User::query()->where(static function ($query): void {
|
||||
$query->where('helpdesk_agent', true)->orWhere('helpdesk_admin', true);
|
||||
})->orderBy('name')->get(['id', 'name', 'email']),
|
||||
'boards' => KanbanBoard::query()->with(['statuses', 'owner:id,name,email'])->withCount('tickets')->orderBy('sort_order')->orderBy('name')->get(),
|
||||
'repositories' => GitRepository::query()->with(['project'])->withCount(['commits', 'webhookLogs'])->orderBy('name')->get(),
|
||||
'workflowTemplates' => WorkflowTemplate::query()->orderBy('event')->orderBy('name')->get(),
|
||||
'cannedResponses' => CannedResponse::query()->with(['category', 'team'])->orderBy('sort_order')->orderBy('name')->get(),
|
||||
'slaPolicies' => SlaPolicy::query()->with(['priority', 'category'])->orderByDesc('is_default')->orderBy('name')->get(),
|
||||
'automationRules' => AutomationRule::query()->orderBy('sort_order')->orderBy('name')->get(),
|
||||
'products' => Product::query()->withCount('tickets')->orderBy('name')->get(),
|
||||
'tags' => Tag::query()->orderBy('name')->get(),
|
||||
'checklistTemplates' => ChecklistTemplate::query()->orderBy('name')->get(),
|
||||
'boards' => KanbanBoard::query()
|
||||
->with(['statuses', 'owner:id,name,email'])
|
||||
->withCount('tickets')
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'boardsPage'),
|
||||
'repositories' => GitRepository::query()
|
||||
->with(['project'])
|
||||
->withCount(['commits', 'webhookLogs'])
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'gitPage'),
|
||||
'workflowTemplates' => WorkflowTemplate::query()
|
||||
->orderBy('event')
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'workflowPage'),
|
||||
'cannedResponses' => CannedResponse::query()
|
||||
->with(['category', 'team'])
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'cannedPage'),
|
||||
'slaPolicies' => SlaPolicy::query()
|
||||
->with(['priority', 'category'])
|
||||
->orderByDesc('is_default')
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'slaPage'),
|
||||
'automationRules' => AutomationRule::query()
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'rulesPage'),
|
||||
'products' => Product::query()
|
||||
->withCount('tickets')
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'productsPage'),
|
||||
'tags' => Tag::query()
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'tagsPage'),
|
||||
'checklistTemplates' => ChecklistTemplate::query()
|
||||
->orderBy('name')
|
||||
->paginate(10, ['*'], 'checksPage'),
|
||||
'colourOptions' => Color::options(),
|
||||
])->layout('layouts.app');
|
||||
}
|
||||
@@ -830,7 +1098,7 @@ final class SettingsManager extends Component
|
||||
->when($this->hasDefaultColumn('helpdesk_statuses'), fn ($query) => $query->orderByDesc('is_default'))
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
->paginate(10, ['*'], 'statusesPage');
|
||||
}
|
||||
|
||||
private function orderedPriorities(): mixed
|
||||
@@ -840,7 +1108,7 @@ final class SettingsManager extends Component
|
||||
->when($this->hasDefaultColumn('helpdesk_priorities'), fn ($query) => $query->orderByDesc('is_default'))
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
->paginate(10, ['*'], 'prioritiesPage');
|
||||
}
|
||||
|
||||
private function orderedCategories(): mixed
|
||||
@@ -849,7 +1117,7 @@ final class SettingsManager extends Component
|
||||
->withCount('tickets')
|
||||
->when($this->hasDefaultColumn('helpdesk_categories'), fn ($query) => $query->orderByDesc('is_default'))
|
||||
->orderBy('name')
|
||||
->get();
|
||||
->paginate(10, ['*'], 'categoriesPage');
|
||||
}
|
||||
|
||||
private function orderedTeams(): mixed
|
||||
@@ -859,7 +1127,7 @@ final class SettingsManager extends Component
|
||||
->withCount(['projects', 'tickets', 'users'])
|
||||
->when($this->hasDefaultColumn('helpdesk_teams'), fn ($query) => $query->orderByDesc('is_default'))
|
||||
->orderBy('name')
|
||||
->get();
|
||||
->paginate(10, ['*'], 'teamsPage');
|
||||
}
|
||||
|
||||
private function orderedProjects(): mixed
|
||||
@@ -869,7 +1137,7 @@ final class SettingsManager extends Component
|
||||
->withCount('tickets')
|
||||
->when($this->hasDefaultColumn('helpdesk_projects'), fn ($query) => $query->orderByDesc('is_default'))
|
||||
->orderBy('title')
|
||||
->get();
|
||||
->paginate(10, ['*'], 'projectsPage');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -982,3 +982,38 @@ span.flatpickr-weekday {
|
||||
box-shadow: 0 0 0 1000px #09090b inset !important;
|
||||
transition: background-color 9999s ease-in-out 0s;
|
||||
}
|
||||
|
||||
|
||||
/* Helpdesk settings modal presentation */
|
||||
.hd-settings-modal {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hd-settings-modal form {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hd-settings-modal form > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hd-settings-modal input:not([type="checkbox"]):not([type="radio"]):not([type="color"]),
|
||||
.hd-settings-modal select,
|
||||
.hd-settings-modal textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hd-settings-modal textarea {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hd-settings-modal label {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hd-settings-modal {
|
||||
padding-inline: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Tests\Feature;
|
||||
|
||||
use Modules\Helpdesk\Livewire\SettingsManager;
|
||||
use ReflectionClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class SettingsModalStructureTest extends TestCase
|
||||
{
|
||||
public function test_each_settings_editor_exposes_a_modal_state_property(): void
|
||||
{
|
||||
$reflection = new ReflectionClass(SettingsManager::class);
|
||||
|
||||
foreach ([
|
||||
'statusModalOpen', 'priorityModalOpen', 'categoryModalOpen',
|
||||
'projectModalOpen', 'boardModalOpen', 'repositoryModalOpen',
|
||||
'workflowModalOpen', 'cannedModalOpen', 'slaModalOpen',
|
||||
'automationModalOpen', 'checklistModalOpen', 'tagModalOpen',
|
||||
'productModalOpen',
|
||||
] as $property) {
|
||||
self::assertTrue($reflection->hasProperty($property), $property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@props(['paginator'])
|
||||
|
||||
@if ($paginator instanceof \Illuminate\Contracts\Pagination\Paginator)
|
||||
<div class="flex flex-col gap-3 border-t border-zinc-200 bg-zinc-50/70 px-4 py-4 dark:border-zinc-800 dark:bg-zinc-950/35 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="text-xs font-medium text-zinc-500 sm:text-sm">
|
||||
@if (method_exists($paginator, 'total'))
|
||||
Showing
|
||||
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $paginator->firstItem() ?? 0 }}</span>
|
||||
–
|
||||
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $paginator->lastItem() ?? 0 }}</span>
|
||||
of
|
||||
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $paginator->total() }}</span>
|
||||
@else
|
||||
Page {{ $paginator->currentPage() }}
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($paginator->hasPages())
|
||||
<div class="min-w-0">
|
||||
{{ $paginator->onEachSide(1)->links() }}
|
||||
</div>
|
||||
@else
|
||||
<div class="rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-xs font-semibold text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
Page 1
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
+414
-141
@@ -3,20 +3,20 @@
|
||||
* 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 = collect($statuses ?? []);
|
||||
$priorities = collect($priorities ?? []);
|
||||
$categories = collect($categories ?? []);
|
||||
$teams = collect($teams ?? []);
|
||||
$projects = collect($projects ?? []);
|
||||
$boards = collect($boards ?? []);
|
||||
$repositories = collect($repositories ?? []);
|
||||
$workflowTemplates = collect($workflowTemplates ?? []);
|
||||
$cannedResponses = collect($cannedResponses ?? []);
|
||||
$slaPolicies = collect($slaPolicies ?? []);
|
||||
$automationRules = collect($automationRules ?? []);
|
||||
$checklistTemplates = collect($checklistTemplates ?? []);
|
||||
$tags = collect($tags ?? []);
|
||||
$products = collect($products ?? []);
|
||||
$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">
|
||||
@@ -43,20 +43,20 @@
|
||||
</div>
|
||||
<div class="grid gap-2 md:grid-cols-4 xl:grid-cols-7">
|
||||
@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()],
|
||||
'canned' => ['label' => 'Canned', 'count' => $cannedResponses->count()],
|
||||
'sla' => ['label' => 'SLA', 'count' => $slaPolicies->count()],
|
||||
'automation' => ['label' => 'Rules', 'count' => $automationRules->count()],
|
||||
'checklists' => ['label' => 'Checks', 'count' => $checklistTemplates->count()],
|
||||
'tags' => ['label' => 'Tags', 'count' => $tags->count()],
|
||||
'products' => ['label' => 'Products', 'count' => $products->count()],
|
||||
'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) {
|
||||
@@ -103,12 +103,34 @@
|
||||
@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.prevent="saveStatus" class="grid gap-3 xl:grid-cols-12">
|
||||
<div class="xl:col-span-3">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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-3">
|
||||
<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" />
|
||||
@@ -124,41 +146,52 @@
|
||||
class="rounded border-zinc-300 bg-zinc-950 text-blue-600 focus:ring-blue-500">
|
||||
Default status for new tickets
|
||||
</label>
|
||||
@error('teamForm.name')
|
||||
<div class="xl:col-span-12 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
@error('teamForm.slug')
|
||||
<div class="xl:col-span-12 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
@error('teamForm.color')
|
||||
<div class="xl:col-span-12 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
@error('teamForm.user_ids.*')
|
||||
<div class="xl:col-span-12 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
|
||||
<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="resetStatusForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
<x-helpdesk.lookup-table :items="$statuses" edit="editStatus" delete="deleteStatus" />
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="savePriority" class="grid gap-3 xl:grid-cols-12">
|
||||
<div class="xl:col-span-3">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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-3">
|
||||
<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" />
|
||||
@@ -174,27 +207,48 @@
|
||||
<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="resetPriorityForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
<x-helpdesk.lookup-table :items="$priorities" edit="editPriority" delete="deletePriority" />
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveCategory" class="grid gap-3 xl:grid-cols-12">
|
||||
<div class="xl:col-span-3">
|
||||
</div>
|
||||
<div class="xl:col-span-3">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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-3">
|
||||
<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" />
|
||||
@@ -210,14 +264,15 @@
|
||||
<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="resetCategoryForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
<x-helpdesk.lookup-table :items="$categories" edit="editCategory" delete="deleteCategory" />
|
||||
</x-helpdesk.settings-section>
|
||||
<x-helpdesk.settings-pagination :paginator="$categories" />
|
||||
</x-helpdesk.settings-section>
|
||||
@endif
|
||||
|
||||
@if ($section === 'teams')
|
||||
@@ -241,7 +296,7 @@
|
||||
<flux:modal
|
||||
name="helpdesk-team-editor"
|
||||
wire:model="teamModalOpen"
|
||||
class="w-full max-w-5xl"
|
||||
class="hd-settings-modal w-full max-w-5xl"
|
||||
@close="closeTeamModal"
|
||||
>
|
||||
<form wire:submit="saveTeam" class="space-y-5">
|
||||
@@ -307,19 +362,6 @@
|
||||
Default team for new tickets
|
||||
</label>
|
||||
|
||||
@error('teamForm.name')
|
||||
<div class="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-600 dark:text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
@error('teamForm.slug')
|
||||
<div class="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-600 dark:text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
@error('teamForm.color')
|
||||
<div class="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-600 dark:text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
@error('teamForm.user_ids.*')
|
||||
<div class="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-600 dark:text-red-300">{{ $message }}</div>
|
||||
@enderror
|
||||
|
||||
<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
|
||||
@@ -331,14 +373,37 @@
|
||||
</div>
|
||||
</form>
|
||||
</flux:modal>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveProject" class="grid gap-3 xl:grid-cols-12">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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">
|
||||
@@ -368,22 +433,45 @@
|
||||
<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="resetProjectForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
<x-helpdesk.project-table :items="$projects" />
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveBoard" class="grid gap-3 md:grid-cols-7">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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" />
|
||||
@@ -420,12 +508,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 board
|
||||
</flux:button>
|
||||
<flux:button size="sm" type="button" wire:click="resetBoardForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
|
||||
<div
|
||||
class="overflow-hidden rounded-2xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
|
||||
@@ -485,14 +573,37 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveRepository" class="grid gap-3 md:grid-cols-6">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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)
|
||||
@@ -512,22 +623,45 @@
|
||||
<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="resetRepositoryForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
<x-helpdesk.repository-table :items="$repositories" />
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveWorkflowTemplate" class="grid gap-3 md:grid-cols-6">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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>
|
||||
@@ -564,12 +698,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
|
||||
workflow message</flux:button>
|
||||
<flux:button size="sm" type="button" wire:click="resetWorkflowTemplateForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
|
||||
<div
|
||||
class="overflow-hidden rounded-2xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
|
||||
@@ -616,15 +750,38 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveCannedResponse" class="grid gap-3 md:grid-cols-6">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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" />
|
||||
@@ -654,12 +811,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 canned
|
||||
response</flux:button>
|
||||
<flux:button size="sm" type="button" wire:click="resetCannedResponseForm" 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">Reset
|
||||
<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>
|
||||
</x-slot: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">
|
||||
@@ -692,14 +849,37 @@
|
||||
No canned responses yet.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveSlaPolicy" class="grid gap-3 md:grid-cols-6">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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">
|
||||
@@ -731,12 +911,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 SLA
|
||||
policy</flux:button>
|
||||
<flux:button size="sm" type="button" wire:click="resetSlaPolicyForm" 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">Reset
|
||||
<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>
|
||||
</x-slot: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">
|
||||
@@ -778,14 +958,37 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveAutomationRule" class="grid gap-3 md:grid-cols-6">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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"
|
||||
@@ -830,12 +1033,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
|
||||
automation rule</flux:button>
|
||||
<flux:button size="sm" type="button" wire:click="resetAutomationRuleForm" 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">Reset
|
||||
<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>
|
||||
</x-slot: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">
|
||||
@@ -858,14 +1061,37 @@
|
||||
No automation rules yet.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveChecklistTemplate" class="grid gap-3 md:grid-cols-6">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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">
|
||||
@@ -891,12 +1117,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
|
||||
checklist</flux:button>
|
||||
<flux:button size="sm" type="button" wire:click="resetChecklistTemplateForm" 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">Reset
|
||||
<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>
|
||||
</x-slot: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">
|
||||
@@ -918,14 +1144,37 @@
|
||||
No checklist templates yet.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveTag" class="grid gap-3 xl:grid-cols-12">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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>
|
||||
@@ -937,12 +1186,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="resetTagForm" 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">Reset
|
||||
<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>
|
||||
</x-slot:form>
|
||||
</flux:modal>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
@forelse ($tags as $tag)
|
||||
<div
|
||||
@@ -963,14 +1212,37 @@
|
||||
No tags yet.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<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:form>
|
||||
<form wire:submit.prevent="saveProduct" class="grid gap-3 md:grid-cols-6">
|
||||
<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="mb-5 border-b border-zinc-200 pb-4 dark:border-zinc-700">
|
||||
<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" />
|
||||
@@ -985,12 +1257,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
|
||||
product/service</flux:button>
|
||||
<flux:button size="sm" type="button" wire:click="resetProductForm" 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">Reset
|
||||
<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>
|
||||
</x-slot: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">
|
||||
@@ -1013,7 +1285,8 @@
|
||||
No products or services yet.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</x-helpdesk.settings-section>
|
||||
<x-helpdesk.settings-pagination :paginator="$products" />
|
||||
</x-helpdesk.settings-section>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user