diff --git a/Modules/Helpdesk/Database/Migrations/2026_08_26_093000_create_helpdesk_team_user_table.php b/Modules/Helpdesk/Database/Migrations/2026_08_26_093000_create_helpdesk_team_user_table.php new file mode 100644 index 0000000..4bf51fd --- /dev/null +++ b/Modules/Helpdesk/Database/Migrations/2026_08_26_093000_create_helpdesk_team_user_table.php @@ -0,0 +1,36 @@ +foreignId('team_id') + ->constrained('helpdesk_teams') + ->cascadeOnDelete(); + + $table->foreignId('user_id') + ->constrained('users') + ->cascadeOnDelete(); + + $table->timestamps(); + + $table->primary(['team_id', 'user_id']); + }); + } + + public function down(): void + { + Schema::dropIfExists('helpdesk_team_user'); + } +}; diff --git a/Modules/Helpdesk/Livewire/SettingsManager.php b/Modules/Helpdesk/Livewire/SettingsManager.php index dd51da8..c77975a 100644 --- a/Modules/Helpdesk/Livewire/SettingsManager.php +++ b/Modules/Helpdesk/Livewire/SettingsManager.php @@ -42,7 +42,7 @@ final class SettingsManager extends Component public array $categoryForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false]; /** @var array */ - public array $teamForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null]; + public array $teamForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null, 'user_ids' => []]; /** @var array */ public array $projectForm = ['id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null]; @@ -80,7 +80,7 @@ final class SettingsManager extends Component $this->statusForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_closed' => false, 'is_default' => false]; $this->priorityForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'gray', 'sort_order' => 0, 'is_default' => false]; $this->categoryForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false]; - $this->teamForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null]; + $this->teamForm += ['id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null, 'user_ids' => []]; $this->projectForm += ['id' => null, 'team_id' => null, 'title' => '', 'slug' => '', 'color' => 'blue', 'description' => '', 'is_default' => false, 'archived_at' => null]; $this->boardForm += ['id' => null, 'name' => '', 'slug' => '', 'description' => '', 'owner_id' => null, 'is_default' => false, 'sort_order' => 0, 'status_ids' => []]; $this->repositoryForm += ['id' => null, 'project_id' => null, 'name' => '', 'provider' => 'git', 'remote_url' => '', 'default_branch' => 'main', 'webhook_secret' => '']; @@ -250,22 +250,33 @@ final class SettingsManager extends Component 'teamForm.color' => ['required', 'string', Color::validationRule()], 'teamForm.description' => ['nullable', 'string'], 'teamForm.is_default' => ['boolean'], + 'teamForm.user_ids' => ['array'], + 'teamForm.user_ids.*' => ['integer', 'distinct', 'exists:users,id'], ])['teamForm']; + $userIds = array_values(array_unique(array_map('intval', $data['user_ids'] ?? []))); + unset($data['user_ids']); + $data['slug'] = $this->slug($data['slug'] ?: $data['name']); $data = $this->guardDefaultColumn('helpdesk_teams', $data); if ((bool) ($data['is_default'] ?? false)) { $this->clearOtherDefaults(Team::class, $this->teamForm['id']); } - $this->persistSettingModel(Team::class, $this->teamForm['id'], $data); + + /** @var Team $team */ + $team = $this->persistSettingModel(Team::class, $this->teamForm['id'], $data); + $team->users()->sync($userIds); + $this->resetTeamForm(); session()->flash('success', 'Team saved.'); } public function editTeam(int $id): void { - $this->teamForm = Team::query()->findOrFail($id)->only(['id', 'name', 'slug', 'color', 'description', 'is_default', 'archived_at']); + $team = Team::query()->with('users:id')->findOrFail($id); + $this->teamForm = $team->only(['id', 'name', 'slug', 'color', 'description', 'is_default', 'archived_at']); + $this->teamForm['user_ids'] = $team->users->pluck('id')->map(static fn ($id): int => (int) $id)->all(); $this->teamForm['color'] = Color::normalise($this->teamForm['color'] ?? 'indigo'); $this->section = 'teams'; } @@ -276,9 +287,24 @@ final class SettingsManager extends Component session()->flash('success', 'Team deleted.'); } + public function removeUserFromTeam(int $teamId, int $userId): void + { + $team = Team::query()->findOrFail($teamId); + $team->users()->detach($userId); + + if ((int) ($this->teamForm['id'] ?? 0) === $teamId) { + $this->teamForm['user_ids'] = array_values(array_filter( + array_map('intval', $this->teamForm['user_ids'] ?? []), + static fn (int $id): bool => $id !== $userId, + )); + } + + session()->flash('success', 'User removed from team.'); + } + public function resetTeamForm(): void { - $this->teamForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null]; + $this->teamForm = ['id' => null, 'name' => '', 'slug' => '', 'color' => 'indigo', 'description' => '', 'is_default' => false, 'archived_at' => null, 'user_ids' => []]; } public function saveProject(): void @@ -699,11 +725,14 @@ final class SettingsManager extends Component public function render(): View { - return view('modules.helpdesk.livewire.settings-manager', [ + return view('helpdesk::livewire.settings-manager', [ 'statuses' => $this->orderedStatuses(), 'priorities' => $this->orderedPriorities(), 'categories' => $this->orderedCategories(), 'teams' => $this->orderedTeams(), + 'teamUsers' => User::query() + ->orderBy('name') + ->get(['id', 'name', 'email', 'helpdesk_agent', 'helpdesk_admin']), 'projects' => $this->orderedProjects(), 'boardOwners' => User::query()->where(static function ($query): void { $query->where('helpdesk_agent', true)->orWhere('helpdesk_admin', true); @@ -718,7 +747,7 @@ final class SettingsManager extends Component 'tags' => Tag::query()->orderBy('name')->get(), 'checklistTemplates' => ChecklistTemplate::query()->orderBy('name')->get(), 'colourOptions' => Color::options(), - ])->layout('layouts.app'); + ])->layout('helpdesk::layouts.app.sidebar'); } private function orderedStatuses(): mixed @@ -753,7 +782,8 @@ final class SettingsManager extends Component private function orderedTeams(): mixed { return Team::query() - ->withCount(['projects', 'tickets']) + ->with(['users:id,name,email']) + ->withCount(['projects', 'tickets', 'users']) ->when($this->hasDefaultColumn('helpdesk_teams'), fn ($query) => $query->orderByDesc('is_default')) ->orderBy('name') ->get(); diff --git a/Modules/Helpdesk/Models/Team.php b/Modules/Helpdesk/Models/Team.php index aa06043..c7a7712 100644 --- a/Modules/Helpdesk/Models/Team.php +++ b/Modules/Helpdesk/Models/Team.php @@ -4,8 +4,11 @@ declare(strict_types=1); namespace Modules\Helpdesk\Models; +use App\Models\User; + use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; @@ -25,6 +28,17 @@ final class Team extends Model 'is_default' => 'boolean', ]; + /** @return BelongsToMany */ + public function users(): BelongsToMany + { + return $this->belongsToMany( + User::class, + 'helpdesk_team_user', + 'team_id', + 'user_id' + )->withTimestamps(); + } + /** @return HasMany */ public function projects(): HasMany { 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 11ab03c..b40cbd7 100644 --- a/resources/themes/backend/views/components/helpdesk/team-table.blade.php +++ b/resources/themes/backend/views/components/helpdesk/team-table.blade.php @@ -3,29 +3,53 @@ - Team - Colour - Default - Tickets - Actions + Team + Colour + Default + Members + Tickets + Actions @forelse ($items as $item) - {{ $item->name }} - + +
{{ $item->name }}
+ @if($item->description) +
{{ $item->description }}
+ @endif + + {{ \Illuminate\Support\Str::of(\Modules\Helpdesk\Support\Color::normalise($item->color))->headline() }} - + {{ $item->is_default ?? false ? 'Default' : 'No' }} - {{ $item->tickets_count ?? 0 }} - + +
+ @forelse($item->users as $member) + + {{ $member->name }} + + + @empty + No members + @endforelse +
+ + {{ $item->tickets_count ?? 0 }} +