diff --git a/Helpdesk/Providers/HelpdeskServiceProvider.php b/Helpdesk/Providers/HelpdeskServiceProvider.php deleted file mode 100644 index c186c70..0000000 --- a/Helpdesk/Providers/HelpdeskServiceProvider.php +++ /dev/null @@ -1,181 +0,0 @@ -app->register(RouteServiceProvider::class); - - $this->mergeConfigFrom( - module_path(ModuleInfo::name(), 'Config/config.php'), - ModuleInfo::nameLower(), - ); - } - - /** - * Bootstrap module services. - */ - public function boot(): void - { - $this->registerTranslations(); - $this->registerConfig(); - $this->registerViews(); - $this->registerHelpers(); - $this->registerCommands(); - $this->registerLivewireComponents(); - $this->registerPolicies(); - - $this->loadMigrationsFrom(module_path(ModuleInfo::name(), 'Database/Migrations')); - } - - /** - * Register Livewire components used by the module. - */ - protected function registerLivewireComponents(): void - { - Livewire::component('helpdesk.ticket-index', TicketIndex::class); - Livewire::component('helpdesk.ticket-create', TicketCreate::class); - Livewire::component('helpdesk.ticket-show', TicketShow::class); - Livewire::component('helpdesk.ticket-timer', TicketTimer::class); - Livewire::component('helpdesk.dashboard-widget', HelpdeskDashboardWidget::class); - Livewire::component('helpdesk.kanban-board', KanbanBoard::class); - Livewire::component('helpdesk.settings-manager', SettingsManager::class); - } - - /** - * Register policies and gates. - */ - protected function registerPolicies(): void - { - Gate::policy(Ticket::class, TicketPolicy::class); - - Gate::define('helpdesk-admin', static fn ($user): bool => method_exists($user, 'isAdmin') && $user->isAdmin()); - Gate::define('helpdesk-agent', static fn ($user): bool => (method_exists($user, 'isAdmin') && $user->isAdmin()) || (bool) ($user->helpdesk_agent ?? false)); - } - - /** - * Register helpers. - */ - protected function registerHelpers(): void - { - foreach (glob(module_path(ModuleInfo::name(), 'Helpers/*.php')) ?: [] as $filename) { - require_once $filename; - } - } - - /** - * Dynamically register console commands. - */ - protected function registerCommands(): void - { - if (! $this->app->runningInConsole()) { - return; - } - - $commandPath = module_path(ModuleInfo::name(), 'Console'); - - if (! is_dir($commandPath)) { - return; - } - - $commands = collect(File::allFiles($commandPath)) - ->map(fn ($file): string => 'Modules\\'.$this->moduleName.'\\Console\\'.$file->getFilenameWithoutExtension()) - ->filter(fn (string $class): bool => class_exists($class)) - ->values() - ->all(); - - $this->commands($commands); - } - - /** - * Register config publishing. - */ - protected function registerConfig(): void - { - $this->publishes([ - module_path(ModuleInfo::name(), 'Config/config.php') => config_path(ModuleInfo::nameLower().'.php'), - ], 'config'); - } - - /** - * Register module views. - */ - protected function registerViews(): void - { - $sourcePath = module_path(ModuleInfo::name(), 'Resources/views'); - $themeRoot = rtrim((string) config('stylist.themes_path', resource_path('themes')), DIRECTORY_SEPARATOR); - $backendTheme = (string) config('stylist.default_backend_theme', 'backend'); - $viewPath = $themeRoot.DIRECTORY_SEPARATOR.$backendTheme - .DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'modules'.DIRECTORY_SEPARATOR.ModuleInfo::nameLower(); - - // Module source views remain module-owned. Any application override is - // published into the selected backend theme, never resources/views. - $this->publishes([ - $sourcePath => $viewPath, - ], ['views', ModuleInfo::nameLower().'-module-views']); - - $this->loadViewsFrom($sourcePath, ModuleInfo::nameLower()); - - Blade::componentNamespace('Modules\Helpdesk\View\Components', 'helpdesk'); - Blade::anonymousComponentPath($sourcePath.'/components', 'helpdesk'); - } - - /** - * Register translations. - */ - protected function registerTranslations(): void - { - $langPath = resource_path('lang/modules/'.ModuleInfo::nameLower()); - - if (is_dir($langPath)) { - $this->loadTranslationsFrom($langPath, ModuleInfo::nameLower()); - - return; - } - - $this->loadTranslationsFrom(module_path(ModuleInfo::name(), 'Resources/lang'), ModuleInfo::nameLower()); - } - - /** - * Get provided services. - * - * @return array - */ - public function provides(): array - { - return []; - } -} diff --git a/Modules/Helpdesk/Livewire/KanbanBoard.php b/Modules/Helpdesk/Livewire/KanbanBoard.php index 008425f..b81eaee 100644 --- a/Modules/Helpdesk/Livewire/KanbanBoard.php +++ b/Modules/Helpdesk/Livewire/KanbanBoard.php @@ -73,7 +73,7 @@ final class KanbanBoard extends Component 'last_activity_at' => now(), ])->save(); - $freshTicket = $ticket->fresh(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) ?? $ticket; + $freshTicket = $ticket->fresh(['user:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) ?? $ticket; $workflow->dispatch( $freshTicket, @@ -121,7 +121,7 @@ final class KanbanBoard extends Component $this->board_id = $board->id; - $fresh = $ticket->fresh(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'board']) ?? $ticket; + $fresh = $ticket->fresh(['user:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'board']) ?? $ticket; $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); $message = 'Ticket '.$fresh->reference.' moved from '.$oldBoardName.' to '.$board->name.'.'; @@ -206,7 +206,7 @@ final class KanbanBoard extends Component } return Ticket::query() - ->with(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) + ->with(['user:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) ->visibleTo(Auth::user()) ->where('board_id', $board->id) ->latest('last_activity_at') diff --git a/Modules/Helpdesk/Models/Ticket.php b/Modules/Helpdesk/Models/Ticket.php index b243547..6fcd183 100644 --- a/Modules/Helpdesk/Models/Ticket.php +++ b/Modules/Helpdesk/Models/Ticket.php @@ -27,7 +27,7 @@ final class Ticket extends Model protected $table = 'helpdesk_tickets'; - protected $with = ['user', 'owner', 'agent']; + protected $with = ['user']; /** @var list */ protected $fillable = [ @@ -62,17 +62,24 @@ final class Ticket extends Model return $this->belongsTo(User::class, 'user_id'); } + + /** + * Compatibility alias for the requester. + * + * This is deliberately an accessor, not another BelongsTo relation. + * `owner()` and `user()` previously pointed to the same users.user_id + * relation and caused duplicate User relationship queries. + */ + public function getOwnerAttribute(): ?User + { + return $this->user; + } + public function tickets(): Ticket|HasMany { return $this->hasMany(Ticket::class, 'ticket_id'); } - /** @return BelongsTo */ - public function owner(): BelongsTo - { - return $this->belongsTo(User::class, 'user_id'); - } - /** @return BelongsTo */ public function agent(): BelongsTo { diff --git a/Modules/Helpdesk/Services/WorkflowNotificationService.php b/Modules/Helpdesk/Services/WorkflowNotificationService.php index 5b27b43..b3985ad 100644 --- a/Modules/Helpdesk/Services/WorkflowNotificationService.php +++ b/Modules/Helpdesk/Services/WorkflowNotificationService.php @@ -69,7 +69,7 @@ final class WorkflowNotificationService */ private function hydrateTicketUsers(Ticket $ticket): void { - if ($ticket->relationLoaded('owner') && $ticket->relationLoaded('agent')) { + if ($ticket->relationLoaded('user') && $ticket->relationLoaded('agent')) { return; } @@ -81,7 +81,6 @@ final class WorkflowNotificationService if ($userIds->isEmpty()) { $ticket->setRelation('user', null); - $ticket->setRelation('owner', null); $ticket->setRelation('agent', null); return; @@ -96,7 +95,6 @@ final class WorkflowNotificationService $agent = $ticket->agent_id === null ? null : $users->get((int) $ticket->agent_id); $ticket->setRelation('user', $owner); - $ticket->setRelation('owner', $owner); $ticket->setRelation('agent', $agent); } @@ -105,7 +103,7 @@ final class WorkflowNotificationService return match ($role) { 'agent' => $ticket->agent, 'actor' => $actor, - default => $ticket->owner, + default => $ticket->user, }; } @@ -119,7 +117,7 @@ final class WorkflowNotificationService '{{ ticket.category }}' => (string) ($ticket->category?->name ?? ''), '{{ ticket.project }}' => (string) ($ticket->project?->title ?? ''), '{{ ticket.team }}' => (string) ($ticket->team?->name ?? ''), - '{{ requester.name }}' => (string) ($ticket->owner?->name ?? ''), + '{{ requester.name }}' => (string) ($ticket->user?->name ?? ''), '{{ agent.name }}' => (string) ($ticket->agent?->name ?? ''), '{{ actor.name }}' => (string) ($actor?->name ?? ''), '{{ ticket.url }}' => route('helpdesk.tickets.show', ['ticket' => $ticket->getKey()]), diff --git a/Stylist/Providers/StylistServiceProvider.php b/Stylist/Providers/StylistServiceProvider.php deleted file mode 100644 index 322ebdb..0000000 --- a/Stylist/Providers/StylistServiceProvider.php +++ /dev/null @@ -1,457 +0,0 @@ -app->register(RouteServiceProvider::class); - - $this->mergeConfigFrom(module_path(ModuleInfo::name(), 'Config/config.php'), ModuleInfo::nameLower()); - - $this->app->singleton('stylist', fn ($app) => $app->make(Stylist::class)); - $this->app->singleton('stylist.html', fn ($app) => $app->make(ThemeHtmlBuilder::class)); - $this->app->singleton('stylist.seo', fn ($app) => $app->make(ThemeSeoBuilder::class)); - } - - public function boot(): void - { - $this->registerViews(); - $this->registerThemeViews(); - $this->registerTranslations(); - $this->registerPublishing(); - $this->registerHelpers(); - $this->registerBlade(); - - if ($this->app->runningInConsole()) { - $this->commands([ - PublishAssetsCommand::class, - ValidateThemeCommand::class, - InspectThemesCommand::class, - FindDuplicateViewsCommand::class, - ThemeDoctorCommand::class, - CreateThemeCommand::class, - ]); - } - - $this->loadMigrationsFrom(module_path(ModuleInfo::name(), 'Database/Migrations')); - - View::composer('*', function ($view): void { - if (! $this->app->bound(Stylist::class)) { - return; - } - - $view->with('activeTheme', $this->app->make(Stylist::class)->current()); - }); - - // Register both selected presentation trees immediately, then register them again - // after every service provider has finished booting. Some package/module providers - // replace Laravel's base view locations during their own boot phase; the booted - // callback makes Stylist the final authority for ordinary view('...') resolution. - $this->registerSelectedThemeOverrides(); - $this->registerSelectedThemeFluxOverrides(); - - $this->app->booted(function (): void { - $this->registerSelectedThemeOverrides(); - $this->registerSelectedThemeFluxOverrides(); - }); - - } - - private function registerViews(): void - { - $sourcePath = module_path(ModuleInfo::name(), 'Resources/views'); - - // Module-owned package views stay inside the module. Application overrides - // belong to resources/themes/{theme}/views/modules/stylist, which Stylist - // registers as the authoritative themed namespace. Never publish back into - // resources/views; the standard baseline intentionally has no legacy view tree. - $this->loadViewsFrom($sourcePath, ModuleInfo::nameLower()); - } - - private function registerThemeViews(): void - { - foreach ($this->themeRoots() as $root) { - if (! is_dir($root)) { - logger()->warning('Stylist theme root does not exist', [ - 'root' => $root, - ]); - - continue; - } - - $themeDirectories = glob($root.'/*', GLOB_ONLYDIR) ?: []; - - foreach ($themeDirectories as $themeDirectory) { - $themeViewsPath = $themeDirectory.'/views'; - - if (! is_dir($themeViewsPath)) { - continue; - } - - $themeKey = strtolower(basename($themeDirectory)); - - // Themes always live outside the module, normally in resources/themes. - // Register the canonical namespace and the legacy namespace as an alias - // to the same application-owned directory. The alias keeps existing - // routes and Blade templates working without reintroducing module themes. - $namespaces = [ - "stylist-theme-{$themeKey}", - "stylist-module-theme-{$themeKey}", - ]; - - $componentsPath = $themeViewsPath.'/components'; - - // The common theme is the application-wide owner of layouts, - // authentication, settings and shared components. Livewire 4 - // resolves its default full-page layout through layouts::app, - // so point that compatibility namespace at the common theme. - if ($themeKey === 'common') { - $layoutsPath = $componentsPath.'/layouts'; - - if (is_dir($layoutsPath)) { - View::addNamespace('layouts', $layoutsPath); - Blade::anonymousComponentPath($layoutsPath, 'layouts'); - } - - View::addNamespace('common', $themeViewsPath); - - if (is_dir($componentsPath)) { - Blade::anonymousComponentPath($componentsPath, 'common'); - } - } - - foreach ($namespaces as $namespace) { - View::addNamespace($namespace, $themeViewsPath); - - if (is_dir($componentsPath)) { - Blade::anonymousComponentPath($componentsPath, $namespace); - } - } - } - } - } - - private function registerSelectedThemeOverrides(): void - { - $stylist = $this->app->make(Stylist::class); - - $current = $stylist->current(); - $frontend = $stylist->frontend(); - $backend = $stylist->backend(); - - // The active request context must win ordinary view resolution. Keep the - // opposite presentation tree available as a compatibility fallback, but - // never let frontend views shadow backend views on backend requests (or - // vice versa) simply because of provider registration order. - $orderedThemes = [$current, $frontend, $backend]; - $chains = []; - $seenThemes = []; - - foreach ($orderedThemes as $theme) { - if (isset($seenThemes[$theme->key()])) { - continue; - } - - $seenThemes[$theme->key()] = true; - $chains[] = $this->themeChain($theme); - } - - $keys = []; - foreach ($chains as $chain) { - foreach ($chain as $key) { - if (! in_array($key, $keys, true)) { - $keys[] = $key; - } - } - } - - if (! in_array('common', $keys, true)) { - $keys[] = 'common'; - } - - // Laravel's starter kits resolve authenticated layouts through the - // x-layouts:: namespace. Once those views live inside the backend - // theme, Stylist must make that selected theme authoritative for the - // namespace as well as for ordinary view('...') lookups. - $this->registerLayoutCompatibilityNamespace($stylist->backend()); - - $finder = View::getFinder(); - $themePaths = []; - - foreach ($keys as $key) { - try { - $theme = $this->app->make(Loader::class)->find($key); - } catch (\Throwable) { - continue; - } - - $viewsPath = $theme->path().DIRECTORY_SEPARATOR.'views'; - if (is_dir($viewsPath)) { - $themePaths[] = $viewsPath; - } - - $this->registerThemeModuleNamespaces($key); - } - - // FileViewFinder is the source of truth for ordinary view('...') resolution. - // Set the complete ordered path list explicitly instead of relying on provider - // boot order and incremental prependLocation() calls. Theme paths stay first; - // Laravel's normal view locations remain as the final fallback. - if (method_exists($finder, 'getPaths') && method_exists($finder, 'setPaths')) { - // The application intentionally has no resources/views tree. Laravel's - // default FileViewFinder still starts with that location, and view:cache - // asks Symfony Finder to scan every registered path. Remove missing legacy - // locations so theme-only applications can be optimized on a clean release. - $existingPaths = array_values(array_filter( - $finder->getPaths(), - static fn (string $path): bool => is_dir($path), - )); - $paths = array_values(array_unique(array_merge($themePaths, $existingPaths))); - $finder->setPaths($paths); - - if (method_exists($finder, 'flush')) { - $finder->flush(); - } - } else { - foreach (array_reverse($themePaths) as $viewsPath) { - if (method_exists($finder, 'prependLocation')) { - $finder->prependLocation($viewsPath); - } else { - View::addLocation($viewsPath); - } - } - } - } - - /** - * Make Flux application overrides theme-owned. - * - * Flux registers anonymous component paths using the prefix "flux". Laravel - * converts that prefix into a hashed view namespace (xxh128 on Laravel 12+). - * Registering only a normal "flux" view namespace therefore does not satisfy - * Flux::componentExists(). Register each themed views/flux directory through - * Blade exactly as Flux itself does, then prepend it to the hashed namespace so - * the selected theme wins over package stubs while retaining package fallbacks. - */ - private function registerSelectedThemeFluxOverrides(): void - { - $stylist = $this->app->make(Stylist::class); - $orderedThemes = [$stylist->current(), $stylist->frontend(), $stylist->backend()]; - $keys = []; - - foreach ($orderedThemes as $theme) { - foreach ($this->themeChain($theme) as $key) { - if (! in_array($key, $keys, true)) { - $keys[] = $key; - } - } - } - - if (! in_array('common', $keys, true)) { - $keys[] = 'common'; - } - - $themeFluxPaths = []; - - foreach ($keys as $key) { - try { - $theme = $this->app->make(Loader::class)->find($key); - } catch (\Throwable) { - continue; - } - - $fluxPath = $theme->path().DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'flux'; - - if (is_dir($fluxPath) && ! in_array($fluxPath, $themeFluxPaths, true)) { - $themeFluxPaths[] = $fluxPath; - } - } - - if ($themeFluxPaths === []) { - return; - } - - // This mirrors FluxServiceProvider::bootComponentPath(). It is essential: - // Flux::componentExists() checks the hashed namespace created by - // Blade::anonymousComponentPath(..., 'flux'), not a literal flux:: namespace. - foreach ($themeFluxPaths as $fluxPath) { - Blade::anonymousComponentPath($fluxPath, 'flux'); - } - - $fluxNamespace = version_compare($this->app->version(), '12.0.0', '>=') - ? hash('xxh128', 'flux') - : md5('flux'); - - // anonymousComponentPath() adds locations to the namespace. Prepend the - // selected theme paths in reverse order so the first selected theme remains - // authoritative while Flux package views continue to work as fallbacks. - foreach (array_reverse($themeFluxPaths) as $fluxPath) { - View::prependNamespace($fluxNamespace, $fluxPath); - } - - $finder = View::getFinder(); - if (method_exists($finder, 'flush')) { - $finder->flush(); - } - } - - /** @return array */ - private function themeChain(Theme $theme): array - { - $loader = $this->app->make(Loader::class); - $chain = []; - $seen = []; - - while (! isset($seen[$theme->key()])) { - $seen[$theme->key()] = true; - $chain[] = $theme->key(); - - $parent = $theme->parent(); - if (! $parent) { - break; - } - - try { - $theme = $loader->find($parent); - } catch (\Throwable) { - break; - } - } - - return $chain; - } - - private function prependThemeViewTree(string $themeKey): void - { - try { - $theme = $this->app->make(Loader::class)->find($themeKey); - } catch (\Throwable) { - return; - } - - $viewsPath = $theme->path().DIRECTORY_SEPARATOR.'views'; - if (! is_dir($viewsPath)) { - return; - } - - $finder = View::getFinder(); - if (method_exists($finder, 'prependLocation')) { - $finder->prependLocation($viewsPath); - } else { - View::addLocation($viewsPath); - } - - $this->registerThemeModuleNamespaces($themeKey); - } - - private function registerLayoutCompatibilityNamespace(Theme $theme): void - { - $candidates = [ - $theme->path().DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'layouts', - $theme->path().DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'components'.DIRECTORY_SEPARATOR.'layouts', - ]; - - foreach ($candidates as $layoutsPath) { - if (! is_dir($layoutsPath)) { - continue; - } - - View::prependNamespace('layouts', $layoutsPath); - Blade::anonymousComponentPath($layoutsPath, 'layouts'); - } - } - - private function registerThemeModuleNamespaces(string $themeKey): void - { - try { - $theme = $this->app->make(Loader::class)->find($themeKey); - } catch (\Throwable) { - return; - } - - $modulesPath = $theme->path().DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'modules'; - if (! is_dir($modulesPath)) { - return; - } - - foreach (glob($modulesPath.'/*', GLOB_ONLYDIR) ?: [] as $moduleViewsPath) { - $namespace = strtolower(basename($moduleViewsPath)); - View::prependNamespace($namespace, $moduleViewsPath); - - $componentsPath = $moduleViewsPath.DIRECTORY_SEPARATOR.'components'; - if (is_dir($componentsPath)) { - Blade::anonymousComponentPath($componentsPath, $namespace); - } - } - } - - private function normalisePath(string $path): string - { - return rtrim( - str_replace('\\', '/', realpath($path) ?: $path), - '/' - ); - } - - /** @return array */ - private function themeRoots(): array - { - return array_values(array_unique(array_filter([ - (string) config('stylist.themes_path'), - ]))); - } - - private function registerTranslations(): void - { - $langPath = resource_path('lang/modules/'.ModuleInfo::nameLower()); - $sourcePath = module_path(ModuleInfo::name(), 'Resources/lang'); - - $this->loadTranslationsFrom(is_dir($langPath) ? $langPath : $sourcePath, ModuleInfo::nameLower()); - } - - private function registerPublishing(): void - { - $this->publishes([ - module_path(ModuleInfo::name(), 'Config/config.php') => config_path('stylist.php'), - ], ['config', 'stylist-config']); - - } - - private function registerHelpers(): void - { - foreach (glob(module_path(ModuleInfo::name(), 'Helpers/*.php')) ?: [] as $filename) { - require_once $filename; - } - } - - private function registerBlade(): void - { - Blade::directive('themeVariables', fn () => 'variables(); ?>'); - Blade::directive('themeStylesheet', fn ($expression) => 'stylesheet('.($expression ?: 'null').'); ?>'); - Blade::directive('themeScript', fn ($expression) => 'script('.($expression ?: 'null').'); ?>'); - Blade::directive('themeSwitcher', fn () => 'switcher(); ?>'); - Blade::directive('themeSeo', fn ($expression) => 'render('.($expression ?: '[]').'); ?>'); - } -} diff --git a/resources/themes/backend/views/components/helpdesk/project-table.blade.php b/resources/themes/backend/views/components/helpdesk/project-table.blade.php index c7af1cb..86027e4 100644 --- a/resources/themes/backend/views/components/helpdesk/project-table.blade.php +++ b/resources/themes/backend/views/components/helpdesk/project-table.blade.php @@ -4,7 +4,6 @@ class="bg-zinc-50 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-300"> Project - Company Team Colour Default @@ -16,7 +15,6 @@ @forelse ($items as $item) {{ $item->title }} - {{ $item->company?->name ?? 'Global' }} {{ $item->team?->name ?? 'No team' }} diff --git a/resources/themes/backend/views/components/helpdesk/team-table.blade.php b/resources/themes/backend/views/components/helpdesk/team-table.blade.php index 90a3233..11ab03c 100644 --- a/resources/themes/backend/views/components/helpdesk/team-table.blade.php +++ b/resources/themes/backend/views/components/helpdesk/team-table.blade.php @@ -4,7 +4,6 @@ class="bg-zinc-50 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-300"> Team - Company Colour Default Tickets @@ -15,7 +14,6 @@ @forelse ($items as $item) {{ $item->name }} - {{ $item->company?->name ?? 'Global' }} diff --git a/resources/themes/backend/views/layouts/app/sidebar.blade.php b/resources/themes/backend/views/layouts/app/sidebar.blade.php index 33424a2..258def4 100644 --- a/resources/themes/backend/views/layouts/app/sidebar.blade.php +++ b/resources/themes/backend/views/layouts/app/sidebar.blade.php @@ -6,19 +6,11 @@ @php - /* - * Match the Core WMS support navigation strategy: - * detect routes rather than hiding navigation behind role checks. - */ - $helpdeskEnabled = Route::has('helpdesk.index') || Route::has('helpdesk.tickets.index'); - $helpdeskSettingsRoute = Route::has('helpdesk.settings') ? 'helpdesk.settings' : (Route::has('helpdesk.settings.index') ? 'helpdesk.settings.index' : null); - - $helpdeskActive = request()->routeIs('helpdesk.*'); @endphp - @if ($helpdeskEnabled) -
- {{-- Core WMS uses Support as the selected parent group. --}} - @if (Route::has('helpdesk.index')) - - {{ __('Support') }} - - @else -
- {{ __('Support') }} -
- @endif + + @if (Route::has('helpdesk.tickets.index')) + + {{ __('Tickets') }} + + @endif -
- @if (Route::has('helpdesk.tickets.index')) - - {{ __('Tickets') }} - - @endif + @if (Route::has('helpdesk.kanban')) + + {{ __('Kanban') }} + + @endif - @if (Route::has('helpdesk.kanban')) - - {{ __('Kanban') }} - - @endif - - @if ($helpdeskSettingsRoute) - - {{ __('Settings') }} - - @endif -
-
- @endif + @if ($helpdeskSettingsRoute) + + {{ __('Settings') }} + + @endif + @@ -156,16 +125,6 @@ - @if (Route::has('helpdesk.index')) - - {{ __('Support') }} - - @endif - @if ($helpdeskSettingsRoute) + description="Group tickets by support area.">
- - - @foreach ($companies as $company) - - @endforeach -
@@ -215,16 +209,10 @@ @if ($section === 'teams') + description="Create support teams and assign projects to them.">
- - - @foreach ($companies as $company) - - @endforeach -
@@ -257,16 +245,10 @@ @if ($section === 'projects') + description="Create projects and optionally assign them to support teams.">
- - - @foreach ($companies as $company) - - @endforeach -
@@ -420,12 +402,6 @@ description="Configure repositories that can be linked to projects and tickets. Commits can then be stored against tickets for traceability."> - - - @foreach ($companies as $company) - - @endforeach - @foreach ($projects as $project) @@ -439,7 +415,7 @@
+ placeholder="git@github.com:organisation/repo.git" />
- @if ($companies->isNotEmpty()) -
- - -
- -
- - @error('company_id') -

{{ $message }}

- @enderror -
- @endif -
- @if ($companies->isNotEmpty()) -
- - -
- -
- - @error('company_id') -

{{ $message }}

- @enderror -
- @endif -
diff --git a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php index f8acef6..ef80e14 100644 --- a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php +++ b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php @@ -7,7 +7,7 @@

Ticket management

Active tickets

Search, triage and manage support requests across - companies, projects, teams, priorities and statuses.

+ projects, teams, priorities and statuses.

-
-
Company
-
{{ $ticket->owner?->company?->name ?? 'Current company' }}
-
@if ($ticket->tags->isNotEmpty())