php-tenant/View/Modal/Admin/TeamManager.php
Snider d0ad2737cb refactor: rename namespace from Core\Mod\Tenant to Core\Tenant
Simplifies the namespace hierarchy by removing the intermediate Mod
segment. Updates all 118 files including models, services, controllers,
middleware, tests, and composer.json autoload configuration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 16:30:46 +00:00

289 lines
9.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Tenant\View\Modal\Admin;
use Core\Tenant\Models\Workspace;
use Core\Tenant\Models\WorkspaceMember;
use Core\Tenant\Models\WorkspaceTeam;
use Core\Tenant\Services\WorkspaceTeamService;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithPagination;
class TeamManager extends Component
{
use WithPagination;
// Filters
public string $search = '';
public ?int $workspaceFilter = null;
// Team modal
public bool $showTeamModal = false;
public ?int $editingTeamId = null;
// Team form fields
public string $teamName = '';
public string $teamSlug = '';
public string $teamDescription = '';
public array $teamPermissions = [];
public bool $teamIsDefault = false;
public string $teamColour = 'zinc';
public ?int $teamWorkspaceId = null;
// Bulk selection
public array $selected = [];
public bool $selectAll = false;
public function mount(): void
{
$this->checkHadesAccess();
}
public function updatingSearch(): void
{
$this->resetPage();
$this->clearSelection();
}
public function updatingWorkspaceFilter(): void
{
$this->resetPage();
$this->clearSelection();
}
public function updatedSelectAll(bool $value): void
{
$this->selected = $value
? $this->teams->pluck('id')->map(fn ($id) => (string) $id)->toArray()
: [];
}
public function clearSelection(): void
{
$this->selected = [];
$this->selectAll = false;
}
// ─────────────────────────────────────────────────────────────────────────
// Team CRUD
// ─────────────────────────────────────────────────────────────────────────
public function openCreateTeam(): void
{
$this->resetTeamForm();
$this->showTeamModal = true;
}
public function openEditTeam(int $id): void
{
$team = WorkspaceTeam::findOrFail($id);
$this->editingTeamId = $id;
$this->teamName = $team->name;
$this->teamSlug = $team->slug ?? '';
$this->teamDescription = $team->description ?? '';
$this->teamPermissions = $team->permissions ?? [];
$this->teamIsDefault = $team->is_default;
$this->teamColour = $team->colour ?? 'zinc';
$this->teamWorkspaceId = $team->workspace_id;
$this->showTeamModal = true;
}
public function saveTeam(): void
{
$this->validate([
'teamName' => ['required', 'string', 'max:255'],
'teamSlug' => ['nullable', 'string', 'max:255', 'alpha_dash'],
'teamDescription' => ['nullable', 'string', 'max:1000'],
'teamPermissions' => ['array'],
'teamIsDefault' => ['boolean'],
'teamColour' => ['required', 'string', 'max:32'],
'teamWorkspaceId' => ['required', 'exists:workspaces,id'],
]);
$data = [
'name' => $this->teamName,
'description' => $this->teamDescription ?: null,
'permissions' => $this->teamPermissions,
'is_default' => $this->teamIsDefault,
'colour' => $this->teamColour,
'workspace_id' => $this->teamWorkspaceId,
];
// Only set slug for new teams or if explicitly provided
if (! $this->editingTeamId && $this->teamSlug) {
$data['slug'] = $this->teamSlug;
}
// If setting as default, unset other defaults for this workspace
if ($this->teamIsDefault) {
WorkspaceTeam::where('workspace_id', $this->teamWorkspaceId)
->where('is_default', true)
->when($this->editingTeamId, fn ($q) => $q->where('id', '!=', $this->editingTeamId))
->update(['is_default' => false]);
}
if ($this->editingTeamId) {
$team = WorkspaceTeam::findOrFail($this->editingTeamId);
// Don't allow editing system team slug
if ($team->is_system) {
unset($data['slug']);
}
$team->update($data);
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.team_updated'));
} else {
WorkspaceTeam::create($data);
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.team_created'));
}
$this->closeTeamModal();
}
public function deleteTeam(int $id): void
{
$team = WorkspaceTeam::findOrFail($id);
if ($team->is_system) {
session()->flash('error', __('tenant::tenant.admin.team_manager.messages.cannot_delete_system'));
return;
}
$memberCount = WorkspaceMember::where('team_id', $team->id)->count();
if ($memberCount > 0) {
session()->flash('error', __('tenant::tenant.admin.team_manager.messages.cannot_delete_has_members', ['count' => $memberCount]));
return;
}
$team->delete();
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.team_deleted'));
}
public function closeTeamModal(): void
{
$this->showTeamModal = false;
$this->resetTeamForm();
}
protected function resetTeamForm(): void
{
$this->editingTeamId = null;
$this->teamName = '';
$this->teamSlug = '';
$this->teamDescription = '';
$this->teamPermissions = [];
$this->teamIsDefault = false;
$this->teamColour = 'zinc';
$this->teamWorkspaceId = null;
}
public function seedDefaultTeams(int $workspaceId): void
{
$workspace = Workspace::findOrFail($workspaceId);
$teamService = app(WorkspaceTeamService::class)->forWorkspace($workspace);
$teamService->seedDefaultTeams();
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.defaults_seeded'));
}
public function migrateMembers(int $workspaceId): void
{
$workspace = Workspace::findOrFail($workspaceId);
$teamService = app(WorkspaceTeamService::class)->forWorkspace($workspace);
$migrated = $teamService->migrateExistingMembers();
session()->flash('message', __('tenant::tenant.admin.team_manager.messages.members_migrated', ['count' => $migrated]));
}
// ─────────────────────────────────────────────────────────────────────────
// Computed Properties
// ─────────────────────────────────────────────────────────────────────────
#[Computed]
public function teams(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return WorkspaceTeam::query()
->with(['workspace'])
->when($this->search, function ($query) {
$query->where(function ($q) {
$q->where('name', 'like', "%{$this->search}%")
->orWhere('description', 'like', "%{$this->search}%");
});
})
->when($this->workspaceFilter, function ($query) {
$query->where('workspace_id', $this->workspaceFilter);
})
->withCount('members')
->orderBy('workspace_id')
->ordered()
->paginate(20);
}
#[Computed]
public function workspaces(): \Illuminate\Database\Eloquent\Collection
{
return Workspace::orderBy('name')->get();
}
#[Computed]
public function permissionGroups(): array
{
return WorkspaceTeam::getAvailablePermissions();
}
#[Computed]
public function colourOptions(): array
{
return WorkspaceTeam::getColourOptions();
}
#[Computed]
public function stats(): array
{
$teamQuery = WorkspaceTeam::query();
$memberQuery = WorkspaceMember::query();
if ($this->workspaceFilter) {
$teamQuery->where('workspace_id', $this->workspaceFilter);
$memberQuery->where('workspace_id', $this->workspaceFilter);
}
return [
'total_teams' => $teamQuery->count(),
'total_members' => $memberQuery->count(),
'members_with_team' => (clone $memberQuery)->whereNotNull('team_id')->count(),
];
}
private function checkHadesAccess(): void
{
if (! auth()->user()?->isHades()) {
abort(403, __('tenant::tenant.errors.hades_required'));
}
}
public function render(): View
{
return view('tenant::admin.team-manager')
->layout('hub::admin.layouts.app', ['title' => __('tenant::tenant.admin.team_manager.title')]);
}
}