sourceGroupId = $sourceGroupId; } public function table(Table $table): Table { $tenantId = Tenant::current()?->getKey(); $query = EntraGroup::query(); if ($tenantId) { $query->where('tenant_id', $tenantId); } else { $query->whereRaw('1 = 0'); } $stalenessDays = (int) config('directory_groups.staleness_days', 30); $cutoff = now('UTC')->subDays(max(1, $stalenessDays)); return $table ->queryStringIdentifier('entraGroupCachePicker') ->query($query) ->defaultSort('display_name') ->paginated([10, 25, 50]) ->defaultPaginationPageOption(10) ->searchable() ->searchPlaceholder('Search groups…') ->deferLoading(! app()->runningUnitTests()) ->columns([ TextColumn::make('display_name') ->label('Name') ->searchable() ->sortable() ->wrap() ->limit(60), TextColumn::make('type') ->label('Type') ->badge() ->state(fn (EntraGroup $record): string => $this->groupTypeLabel($this->groupType($record))) ->color(fn (EntraGroup $record): string => $this->groupTypeColor($this->groupType($record))) ->toggleable(), TextColumn::make('entra_id') ->label('ID') ->formatStateUsing(fn (?string $state): string => filled($state) ? ('…'.substr($state, -8)) : '—') ->extraAttributes(['class' => 'font-mono']) ->toggleable(), TextColumn::make('last_seen_at') ->label('Last seen') ->since() ->sortable(), ]) ->filters([ SelectFilter::make('stale') ->label('Stale') ->options([ '1' => 'Stale', '0' => 'Fresh', ]) ->query(function (Builder $query, array $data) use ($cutoff): Builder { $value = $data['value'] ?? null; if ($value === null || $value === '') { return $query; } if ((string) $value === '1') { return $query->where(function (Builder $q) use ($cutoff): void { $q->whereNull('last_seen_at') ->orWhere('last_seen_at', '<', $cutoff); }); } return $query->where('last_seen_at', '>=', $cutoff); }), SelectFilter::make('group_type') ->label('Type') ->options([ 'security' => 'Security', 'microsoft365' => 'Microsoft 365', 'mail' => 'Mail-enabled', 'unknown' => 'Unknown', ]) ->query(function (Builder $query, array $data): Builder { $value = (string) ($data['value'] ?? ''); if ($value === '') { return $query; } return match ($value) { 'microsoft365' => $query->whereJsonContains('group_types', 'Unified'), 'security' => $query ->where('security_enabled', true) ->where(function (Builder $q): void { $q->whereNull('group_types') ->orWhereJsonDoesntContain('group_types', 'Unified'); }), 'mail' => $query ->where('mail_enabled', true) ->where(function (Builder $q): void { $q->whereNull('group_types') ->orWhereJsonDoesntContain('group_types', 'Unified'); }), 'unknown' => $query ->where(function (Builder $q): void { $q->whereNull('group_types') ->orWhereJsonDoesntContain('group_types', 'Unified'); }) ->where('security_enabled', false) ->where('mail_enabled', false), default => $query, }; }), ]) ->actions([ Action::make('select') ->label('Select') ->icon('heroicon-o-check') ->color('primary') ->action(function (EntraGroup $record): void { $this->dispatch('entra-group-cache-picked', sourceGroupId: $this->sourceGroupId, entraId: (string) $record->entra_id); }), ]) ->emptyStateHeading('No cached groups found') ->emptyStateDescription('Run “Sync Groups” first, then come back here.') ->emptyStateActions([ Action::make('open_groups') ->label('Directory Groups') ->icon('heroicon-o-user-group') ->url(fn (): string => EntraGroupResource::getUrl('index', tenant: Tenant::current())), Action::make('open_sync_runs') ->label('Group Sync Runs') ->icon('heroicon-o-clock') ->url(fn (): string => EntraGroupSyncRunResource::getUrl('index', tenant: Tenant::current())), ]); } public function render(): View { return view('livewire.entra-group-cache-picker-table'); } private function groupType(EntraGroup $record): string { $groupTypes = $record->group_types; if (is_array($groupTypes) && in_array('Unified', $groupTypes, true)) { return 'microsoft365'; } if ($record->security_enabled) { return 'security'; } if ($record->mail_enabled) { return 'mail'; } return 'unknown'; } private function groupTypeLabel(string $type): string { return match ($type) { 'microsoft365' => 'Microsoft 365', 'security' => 'Security', 'mail' => 'Mail-enabled', default => 'Unknown', }; } private function groupTypeColor(string $type): string { return match ($type) { 'microsoft365' => 'info', 'security' => 'success', 'mail' => 'warning', default => 'gray', }; } }