structure updates

This commit is contained in:
Mark Gregory
2026-08-26 11:30:36 +01:00
parent 8404c6e40f
commit 66fd8b0e8a
5 changed files with 786 additions and 155 deletions
+282 -14
View File
@@ -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);
}
}
}