schema([ Infolists\Components\TextEntry::make('policy.display_name')->label('Policy'), Infolists\Components\TextEntry::make('version_number')->label('Version'), Infolists\Components\TextEntry::make('policy_type'), Infolists\Components\TextEntry::make('platform'), Infolists\Components\TextEntry::make('created_by')->label('Actor'), Infolists\Components\TextEntry::make('captured_at')->dateTime(), Tabs::make() ->activeTab(1) ->persistTabInQueryString('tab') ->tabs([ Tab::make('Normalized settings') ->schema([ Infolists\Components\ViewEntry::make('normalized_settings') ->view('filament.infolists.entries.normalized-settings') ->state(function (PolicyVersion $record) { $normalized = app(PolicyNormalizer::class)->normalize( is_array($record->snapshot) ? $record->snapshot : [], $record->policy_type ?? '', $record->platform ); $normalized['context'] = 'version'; $normalized['record_id'] = (string) $record->getKey(); return $normalized; }), ]), Tab::make('Raw JSON') ->schema([ Infolists\Components\ViewEntry::make('snapshot_pretty') ->view('filament.infolists.entries.snapshot-json') ->state(fn (PolicyVersion $record) => $record->snapshot ?? []), ]), Tab::make('Diff') ->schema([ Infolists\Components\ViewEntry::make('normalized_diff') ->view('filament.infolists.entries.normalized-diff') ->state(function (PolicyVersion $record) { $normalizer = app(PolicyNormalizer::class); $diff = app(VersionDiff::class); $previous = $record->previous(); $from = $previous ? $normalizer->flattenForDiff($previous->snapshot ?? [], $previous->policy_type ?? '', $previous->platform) : []; $to = $normalizer->flattenForDiff($record->snapshot ?? [], $record->policy_type ?? '', $record->platform); return $diff->compare($from, $to); }), Infolists\Components\TextEntry::make('diff') ->label('Diff JSON vs previous') ->state(function (PolicyVersion $record) { $previous = $record->previous(); if (! $previous) { return ['summary' => 'No previous version']; } return app(VersionDiff::class) ->compare($previous->snapshot ?? [], $record->snapshot ?? []); }) ->formatStateUsing(fn ($state) => json_encode($state ?? [], JSON_PRETTY_PRINT)) ->copyable(), ]), ]), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('policy.display_name')->label('Policy')->sortable()->searchable(), Tables\Columns\TextColumn::make('version_number')->sortable(), Tables\Columns\TextColumn::make('policy_type')->badge(), Tables\Columns\TextColumn::make('platform')->badge(), Tables\Columns\TextColumn::make('created_by')->label('Actor'), Tables\Columns\TextColumn::make('captured_at')->dateTime()->sortable(), ]) ->filters([ Tables\Filters\TrashedFilter::make(), ]) ->actions([ Actions\ViewAction::make() ->url(fn (PolicyVersion $record) => static::getUrl('view', ['record' => $record])) ->openUrlInNewTab(false), Actions\ActionGroup::make([ Actions\Action::make('archive') ->label('Archive') ->color('danger') ->icon('heroicon-o-archive-box-x-mark') ->requiresConfirmation() ->visible(fn (PolicyVersion $record) => ! $record->trashed()) ->action(function (PolicyVersion $record, AuditLogger $auditLogger) { $record->delete(); if ($record->tenant) { $auditLogger->log( tenant: $record->tenant, action: 'policy_version.deleted', resourceType: 'policy_version', resourceId: (string) $record->id, status: 'success', context: ['metadata' => ['policy_id' => $record->policy_id, 'version' => $record->version_number]] ); } Notification::make() ->title('Policy version archived') ->success() ->send(); }), Actions\Action::make('forceDelete') ->label('Force delete') ->color('danger') ->icon('heroicon-o-trash') ->requiresConfirmation() ->visible(fn (PolicyVersion $record) => $record->trashed()) ->action(function (PolicyVersion $record, AuditLogger $auditLogger) { if ($record->tenant) { $auditLogger->log( tenant: $record->tenant, action: 'policy_version.force_deleted', resourceType: 'policy_version', resourceId: (string) $record->id, status: 'success', context: ['metadata' => ['policy_id' => $record->policy_id, 'version' => $record->version_number]] ); } $record->forceDelete(); Notification::make() ->title('Policy version permanently deleted') ->success() ->send(); }), ])->icon('heroicon-o-ellipsis-vertical'), ]) ->bulkActions([]); } public static function getPages(): array { return [ 'index' => Pages\ListPolicyVersions::route('/'), 'view' => Pages\ViewPolicyVersion::route('/{record}'), ]; } }