php-tenant/View/Modal/Admin/MemberManager.php
2026-01-27 00:31:43 +00:00

404 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Tenant\View\Modal\Admin;
use Core\Core\Tenant\Models\Workspace;
use Core\Core\Tenant\Models\WorkspaceMember;
use Core\Core\Tenant\Models\WorkspaceTeam;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithPagination;
class MemberManager extends Component
{
use WithPagination;
// Filters
public string $search = '';
public ?int $workspaceFilter = null;
public ?int $teamFilter = null;
// Assign to team modal
public bool $showAssignModal = false;
public ?int $assignMemberId = null;
public ?int $assignTeamId = null;
// Custom permissions modal
public bool $showPermissionsModal = false;
public ?int $permissionsMemberId = null;
public array $grantedPermissions = [];
public array $revokedPermissions = [];
// Bulk selection
public array $selected = [];
public bool $selectAll = false;
// Bulk assign modal
public bool $showBulkAssignModal = false;
public ?int $bulkTeamId = null;
public function mount(?int $workspaceFilter = null, ?int $teamFilter = null): void
{
$this->checkHadesAccess();
$this->workspaceFilter = $workspaceFilter;
$this->teamFilter = $teamFilter;
}
public function updatingSearch(): void
{
$this->resetPage();
$this->clearSelection();
}
public function updatingWorkspaceFilter(): void
{
$this->resetPage();
$this->clearSelection();
$this->teamFilter = null;
}
public function updatingTeamFilter(): void
{
$this->resetPage();
$this->clearSelection();
}
public function updatedSelectAll(bool $value): void
{
$this->selected = $value
? $this->members->pluck('id')->map(fn ($id) => (string) $id)->toArray()
: [];
}
public function clearSelection(): void
{
$this->selected = [];
$this->selectAll = false;
}
// ─────────────────────────────────────────────────────────────────────────
// Team Assignment
// ─────────────────────────────────────────────────────────────────────────
public function openAssignModal(int $memberId): void
{
$member = WorkspaceMember::findOrFail($memberId);
$this->assignMemberId = $memberId;
$this->assignTeamId = $member->team_id;
$this->showAssignModal = true;
}
public function saveAssignment(): void
{
$member = WorkspaceMember::findOrFail($this->assignMemberId);
// Validate team belongs to same workspace
if ($this->assignTeamId) {
$team = WorkspaceTeam::where('id', $this->assignTeamId)
->where('workspace_id', $member->workspace_id)
->first();
if (! $team) {
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.invalid_team'));
return;
}
}
$member->update(['team_id' => $this->assignTeamId]);
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.team_assigned'));
$this->closeAssignModal();
}
public function removeFromTeam(int $memberId): void
{
$member = WorkspaceMember::findOrFail($memberId);
$member->update(['team_id' => null]);
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.removed_from_team'));
}
public function closeAssignModal(): void
{
$this->showAssignModal = false;
$this->assignMemberId = null;
$this->assignTeamId = null;
}
// ─────────────────────────────────────────────────────────────────────────
// Custom Permissions
// ─────────────────────────────────────────────────────────────────────────
public function openPermissionsModal(int $memberId): void
{
$member = WorkspaceMember::findOrFail($memberId);
$this->permissionsMemberId = $memberId;
$this->grantedPermissions = [];
$this->revokedPermissions = [];
// Parse existing custom permissions
foreach ($member->custom_permissions ?? [] as $permission) {
if (str_starts_with($permission, '-')) {
$this->revokedPermissions[] = substr($permission, 1);
} elseif (str_starts_with($permission, '+')) {
$this->grantedPermissions[] = substr($permission, 1);
} else {
$this->grantedPermissions[] = $permission;
}
}
$this->showPermissionsModal = true;
}
public function savePermissions(): void
{
$member = WorkspaceMember::findOrFail($this->permissionsMemberId);
// Build custom permissions array
$customPermissions = [];
foreach ($this->grantedPermissions as $permission) {
$customPermissions[] = '+'.$permission;
}
foreach ($this->revokedPermissions as $permission) {
$customPermissions[] = '-'.$permission;
}
$member->update([
'custom_permissions' => ! empty($customPermissions) ? $customPermissions : null,
]);
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.permissions_updated'));
$this->closePermissionsModal();
}
public function clearPermissions(int $memberId): void
{
$member = WorkspaceMember::findOrFail($memberId);
$member->update(['custom_permissions' => null]);
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.permissions_cleared'));
}
public function closePermissionsModal(): void
{
$this->showPermissionsModal = false;
$this->permissionsMemberId = null;
$this->grantedPermissions = [];
$this->revokedPermissions = [];
}
// ─────────────────────────────────────────────────────────────────────────
// Bulk Operations
// ─────────────────────────────────────────────────────────────────────────
public function openBulkAssignModal(): void
{
$this->bulkTeamId = null;
$this->showBulkAssignModal = true;
}
public function closeBulkAssignModal(): void
{
$this->showBulkAssignModal = false;
$this->bulkTeamId = null;
}
public function bulkAssignTeam(): void
{
if (empty($this->selected)) {
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.no_members_selected'));
return;
}
// Validate team exists
if ($this->bulkTeamId) {
$team = WorkspaceTeam::find($this->bulkTeamId);
if (! $team) {
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.invalid_team'));
return;
}
// Update only members from same workspace as the team
$updated = WorkspaceMember::whereIn('id', $this->selected)
->where('workspace_id', $team->workspace_id)
->update(['team_id' => $this->bulkTeamId]);
} else {
// Remove from teams
$updated = WorkspaceMember::whereIn('id', $this->selected)
->update(['team_id' => null]);
}
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.bulk_team_assigned', ['count' => $updated]));
$this->closeBulkAssignModal();
$this->clearSelection();
}
public function bulkRemoveFromTeam(): void
{
if (empty($this->selected)) {
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.no_members_selected'));
return;
}
$updated = WorkspaceMember::whereIn('id', $this->selected)
->update(['team_id' => null]);
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.bulk_removed_from_team', ['count' => $updated]));
$this->clearSelection();
}
public function bulkClearPermissions(): void
{
if (empty($this->selected)) {
session()->flash('error', __('tenant::tenant.admin.member_manager.messages.no_members_selected'));
return;
}
$updated = WorkspaceMember::whereIn('id', $this->selected)
->update(['custom_permissions' => null]);
session()->flash('message', __('tenant::tenant.admin.member_manager.messages.bulk_permissions_cleared', ['count' => $updated]));
$this->clearSelection();
}
// ─────────────────────────────────────────────────────────────────────────
// Computed Properties
// ─────────────────────────────────────────────────────────────────────────
#[Computed]
public function members(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return WorkspaceMember::query()
->with(['user', 'workspace', 'team', 'inviter'])
->when($this->search, function ($query) {
$query->whereHas('user', function ($q) {
$q->where('name', 'like', "%{$this->search}%")
->orWhere('email', 'like', "%{$this->search}%");
});
})
->when($this->workspaceFilter, function ($query) {
$query->where('workspace_id', $this->workspaceFilter);
})
->when($this->teamFilter, function ($query) {
$query->where('team_id', $this->teamFilter);
})
->orderBy('created_at', 'desc')
->paginate(20);
}
#[Computed]
public function workspaces(): \Illuminate\Database\Eloquent\Collection
{
return Workspace::orderBy('name')->get();
}
#[Computed]
public function teamsForFilter(): \Illuminate\Database\Eloquent\Collection
{
$query = WorkspaceTeam::query();
if ($this->workspaceFilter) {
$query->where('workspace_id', $this->workspaceFilter);
}
return $query->ordered()->get();
}
#[Computed]
public function teamsForAssignment(): \Illuminate\Database\Eloquent\Collection
{
if ($this->assignMemberId) {
$member = WorkspaceMember::find($this->assignMemberId);
if ($member) {
return WorkspaceTeam::where('workspace_id', $member->workspace_id)
->ordered()
->get();
}
}
return new \Illuminate\Database\Eloquent\Collection;
}
#[Computed]
public function teamsForBulkAssignment(): \Illuminate\Database\Eloquent\Collection
{
// Only show teams from the current workspace filter
if ($this->workspaceFilter) {
return WorkspaceTeam::where('workspace_id', $this->workspaceFilter)
->ordered()
->get();
}
return new \Illuminate\Database\Eloquent\Collection;
}
#[Computed]
public function permissionGroups(): array
{
return WorkspaceTeam::getAvailablePermissions();
}
#[Computed]
public function memberForPermissions(): ?WorkspaceMember
{
if ($this->permissionsMemberId) {
return WorkspaceMember::with(['team'])->find($this->permissionsMemberId);
}
return null;
}
#[Computed]
public function stats(): array
{
$query = WorkspaceMember::query();
if ($this->workspaceFilter) {
$query->where('workspace_id', $this->workspaceFilter);
}
if ($this->teamFilter) {
$query->where('team_id', $this->teamFilter);
}
return [
'total_members' => (clone $query)->count(),
'with_team' => (clone $query)->whereNotNull('team_id')->count(),
'with_custom_permissions' => (clone $query)->whereNotNull('custom_permissions')->count(),
];
}
private function checkHadesAccess(): void
{
if (! auth()->user()?->isHades()) {
abort(403, __('tenant::tenant.errors.hades_required'));
}
}
public function render(): View
{
return view('tenant::admin.member-manager')
->layout('hub::admin.layouts.app', ['title' => __('tenant::tenant.admin.member_manager.title')]);
}
}