Major Features: - CRM Lite: Pipeline, tasks, accounts, calendar, inbox - AI Copilot: Multi-provider support, brand voice, content rules - Marketing: Campaigns, templates, channels, broadcasts - Intelligence: Buyer analytics, market intelligence dashboard - Orchestrator: Sales & marketing automation with AI - Compliance: License tracking (minimal shell) - Conversations: Buyer-seller messaging with email/SMS routing Infrastructure: - Suites & Plans system for feature gating - 60+ new migrations - Module middleware for access control - Database seeders for production sync - Enhanced product management (varieties, inventory modes) Documentation: - V1 scope, launch checklist, QA scripts - Module current state audit - Feature matrix (standard vs premium)
188 lines
6.8 KiB
PHP
188 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Models\Ai\AiPromptLog;
|
|
use App\Models\Business;
|
|
use Filament\Pages\Page;
|
|
use Filament\Tables;
|
|
use Filament\Tables\Concerns\InteractsWithTable;
|
|
use Filament\Tables\Contracts\HasTable;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class ModuleUsageReport extends Page implements HasTable
|
|
{
|
|
use InteractsWithTable;
|
|
|
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-chart-bar';
|
|
|
|
protected static \UnitEnum|string|null $navigationGroup = 'AI Settings';
|
|
|
|
protected static ?int $navigationSort = 3;
|
|
|
|
protected static ?string $navigationLabel = 'Usage Reports';
|
|
|
|
protected static ?string $title = 'Module Usage Reports';
|
|
|
|
protected string $view = 'filament.pages.module-usage-report';
|
|
|
|
public string $dateRange = '30';
|
|
|
|
/**
|
|
* Only Superadmins can access Module Usage Reports.
|
|
* Admin Staff will not see this page in navigation.
|
|
*/
|
|
public static function canAccess(): bool
|
|
{
|
|
return auth('admin')->user()?->canManageAi() ?? false;
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->dateRange = request()->get('days', '30');
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->query(
|
|
Business::query()
|
|
->where('copilot_enabled', true)
|
|
->withCount([
|
|
'aiPromptLogs as copilot_requests' => function (Builder $query) {
|
|
$query->where('operation', 'copilot')
|
|
->where('created_at', '>=', now()->subDays((int) $this->dateRange));
|
|
},
|
|
])
|
|
->withSum([
|
|
'aiPromptLogs as copilot_tokens' => function (Builder $query) {
|
|
$query->where('operation', 'copilot')
|
|
->where('created_at', '>=', now()->subDays((int) $this->dateRange));
|
|
},
|
|
], 'total_tokens')
|
|
->withSum([
|
|
'aiPromptLogs as copilot_cost' => function (Builder $query) {
|
|
$query->where('operation', 'copilot')
|
|
->where('created_at', '>=', now()->subDays((int) $this->dateRange));
|
|
},
|
|
], 'estimated_cost')
|
|
)
|
|
->columns([
|
|
Tables\Columns\TextColumn::make('name')
|
|
->label('Business')
|
|
->searchable()
|
|
->sortable(),
|
|
|
|
Tables\Columns\TextColumn::make('copilot_tier')
|
|
->label('Tier')
|
|
->badge()
|
|
->color(fn (?string $state): string => match ($state) {
|
|
'basic' => 'info',
|
|
'premium' => 'success',
|
|
'custom' => 'warning',
|
|
default => 'gray',
|
|
}),
|
|
|
|
Tables\Columns\TextColumn::make('copilot_requests')
|
|
->label('Requests')
|
|
->numeric()
|
|
->sortable()
|
|
->alignEnd(),
|
|
|
|
Tables\Columns\TextColumn::make('copilot_tokens')
|
|
->label('Tokens')
|
|
->numeric()
|
|
->sortable()
|
|
->alignEnd()
|
|
->formatStateUsing(fn ($state) => number_format($state ?? 0)),
|
|
|
|
Tables\Columns\TextColumn::make('copilot_cost')
|
|
->label('Cost')
|
|
->money('usd')
|
|
->sortable()
|
|
->alignEnd(),
|
|
|
|
Tables\Columns\TextColumn::make('last_copilot_use')
|
|
->label('Last Used')
|
|
->getStateUsing(function (Business $record) {
|
|
$lastLog = AiPromptLog::forBusiness($record->id)
|
|
->where('operation', 'copilot')
|
|
->latest()
|
|
->first();
|
|
|
|
return $lastLog?->created_at;
|
|
})
|
|
->dateTime()
|
|
->sortable(query: function (Builder $query, string $direction) {
|
|
// This is a computed column, so we can't sort directly
|
|
return $query;
|
|
}),
|
|
])
|
|
->filters([
|
|
Tables\Filters\SelectFilter::make('copilot_tier')
|
|
->label('Tier')
|
|
->options([
|
|
'basic' => 'Basic',
|
|
'premium' => 'Premium',
|
|
'custom' => 'Custom',
|
|
]),
|
|
])
|
|
->recordUrl(fn (Business $record) => route('filament.admin.resources.businesses.edit', $record))
|
|
->defaultSort('copilot_requests', 'desc')
|
|
->striped()
|
|
->paginated([10, 25, 50, 100]);
|
|
}
|
|
|
|
public function getOverviewStats(): array
|
|
{
|
|
$days = (int) $this->dateRange;
|
|
$startDate = now()->subDays($days);
|
|
|
|
$stats = AiPromptLog::where('operation', 'copilot')
|
|
->where('created_at', '>=', $startDate)
|
|
->selectRaw('
|
|
COUNT(*) as total_requests,
|
|
SUM(total_tokens) as total_tokens,
|
|
SUM(estimated_cost) as total_cost,
|
|
AVG(latency_ms) as avg_latency,
|
|
COUNT(DISTINCT business_id) as active_businesses,
|
|
SUM(CASE WHEN is_error = true THEN 1 ELSE 0 END) as error_count
|
|
')
|
|
->first();
|
|
|
|
$totalBusinessesWithCopilot = Business::where('copilot_enabled', true)->count();
|
|
|
|
return [
|
|
'total_requests' => $stats->total_requests ?? 0,
|
|
'total_tokens' => $stats->total_tokens ?? 0,
|
|
'total_cost' => $stats->total_cost ?? 0,
|
|
'avg_latency' => $stats->avg_latency ?? 0,
|
|
'active_businesses' => $stats->active_businesses ?? 0,
|
|
'total_businesses' => $totalBusinessesWithCopilot,
|
|
'error_count' => $stats->error_count ?? 0,
|
|
'error_rate' => $stats->total_requests > 0
|
|
? round(($stats->error_count / $stats->total_requests) * 100, 2)
|
|
: 0,
|
|
];
|
|
}
|
|
|
|
public function getDailyUsage(): array
|
|
{
|
|
$days = (int) $this->dateRange;
|
|
|
|
return AiPromptLog::where('operation', 'copilot')
|
|
->where('created_at', '>=', now()->subDays($days))
|
|
->selectRaw('DATE(created_at) as date, COUNT(*) as requests, SUM(total_tokens) as tokens, SUM(estimated_cost) as cost')
|
|
->groupBy('date')
|
|
->orderBy('date')
|
|
->get()
|
|
->toArray();
|
|
}
|
|
|
|
public function updatedDateRange(): void
|
|
{
|
|
$this->resetTable();
|
|
}
|
|
}
|