analytics->overview($user, $days); $preferences = $this->preferences->forUser($user); $user->loadMissing(['profile', 'statistics']); $socialLinksCount = (int) DB::table('user_social_links')->where('user_id', $user->id)->count(); $publishedInRange = (int) collect($analytics['comparison'])->sum('published_count'); $challengeEntries = (int) DB::table('nova_cards') ->where('user_id', $user->id) ->whereNull('deleted_at') ->sum('challenge_entries_count'); $profileScore = $this->profileScore($user, $socialLinksCount); $cadenceScore = $this->ratioScore($publishedInRange, 6); $engagementScore = $this->ratioScore( (int) ($analytics['totals']['appreciation'] ?? 0) + (int) ($analytics['totals']['comments'] ?? 0) + (int) ($analytics['totals']['shares'] ?? 0) + (int) ($analytics['totals']['saves'] ?? 0), max(3, (int) ($analytics['totals']['content_count'] ?? 0) * 12) ); $curationScore = $this->ratioScore(count($preferences['featured_modules']), 4); return [ 'summary' => [ 'followers' => (int) ($analytics['totals']['followers'] ?? 0), 'published_in_range' => $publishedInRange, 'engagement_actions' => (int) ($analytics['totals']['appreciation'] ?? 0) + (int) ($analytics['totals']['comments'] ?? 0) + (int) ($analytics['totals']['shares'] ?? 0) + (int) ($analytics['totals']['saves'] ?? 0), 'profile_completion' => $profileScore, 'challenge_entries' => $challengeEntries, 'featured_modules' => count($preferences['featured_modules']), ], 'module_focus' => $this->moduleFocus($analytics), 'checkpoints' => [ $this->checkpoint('profile', 'Profile presentation', $profileScore, 'Profile, cover, and social details shape how new visitors read your work.', route('studio.profile'), 'Update profile'), $this->checkpoint('cadence', 'Publishing cadence', $cadenceScore, sprintf('You published %d items in the last %d days.', $publishedInRange, $days), route('studio.calendar'), 'Open calendar'), $this->checkpoint('engagement', 'Audience response', $engagementScore, 'Comments, reactions, saves, and shares show whether your output is creating momentum.', route('studio.inbox'), 'Open inbox'), $this->checkpoint('curation', 'Featured curation', $curationScore, 'Featured modules make your strongest work easier to discover across the profile surface.', route('studio.featured'), 'Manage featured'), $this->checkpoint('challenges', 'Challenge participation', $this->ratioScore($challengeEntries, 5), 'Challenge submissions create discoverable surfaces beyond your core publishing flow.', route('studio.challenges'), 'Open challenges'), ], 'opportunities' => $this->opportunities($profileScore, $publishedInRange, $challengeEntries, $preferences), 'milestones' => [ $this->milestone('followers', 'Follower milestone', (int) ($analytics['totals']['followers'] ?? 0), $this->nextMilestone((int) ($analytics['totals']['followers'] ?? 0), [10, 25, 50, 100, 250, 500, 1000, 2500, 5000])), $this->milestone('publishing', sprintf('Published in %d days', $days), $publishedInRange, $this->nextMilestone($publishedInRange, [3, 5, 8, 12, 20, 30])), $this->milestone('challenges', 'Challenge submissions', $challengeEntries, $this->nextMilestone($challengeEntries, [1, 3, 5, 10, 25, 50])), ], 'momentum' => [ 'views_trend' => $analytics['views_trend'], 'engagement_trend' => $analytics['engagement_trend'], 'publishing_timeline' => $analytics['publishing_timeline'], ], 'top_content' => collect($analytics['top_content'])->take(5)->values()->all(), 'range_days' => $days, ]; } private function moduleFocus(array $analytics): array { $totalViews = max(1, (int) ($analytics['totals']['views'] ?? 0)); $totalEngagement = max(1, (int) ($analytics['totals']['appreciation'] ?? 0) + (int) ($analytics['totals']['comments'] ?? 0) + (int) ($analytics['totals']['shares'] ?? 0) + (int) ($analytics['totals']['saves'] ?? 0) ); $comparison = collect($analytics['comparison'] ?? [])->keyBy('key'); return collect($analytics['module_breakdown'] ?? []) ->map(function (array $item) use ($comparison, $totalViews, $totalEngagement): array { $engagementValue = (int) ($item['appreciation'] ?? 0) + (int) ($item['comments'] ?? 0) + (int) ($item['shares'] ?? 0) + (int) ($item['saves'] ?? 0); $publishedCount = (int) ($comparison->get($item['key'])['published_count'] ?? 0); return [ 'key' => $item['key'], 'label' => $item['label'], 'icon' => $item['icon'], 'views' => (int) ($item['views'] ?? 0), 'engagement' => $engagementValue, 'published_count' => $publishedCount, 'draft_count' => (int) ($item['draft_count'] ?? 0), 'view_share' => (int) round(((int) ($item['views'] ?? 0) / $totalViews) * 100), 'engagement_share' => (int) round(($engagementValue / $totalEngagement) * 100), 'href' => $item['index_url'], ]; }) ->values() ->all(); } private function opportunities(int $profileScore, int $publishedInRange, int $challengeEntries, array $preferences): array { $items = []; if ($profileScore < 80) { $items[] = [ 'title' => 'Tighten creator presentation', 'body' => 'A more complete profile helps new followers understand the work behind the metrics.', 'href' => route('studio.profile'), 'cta' => 'Update profile', ]; } if ($publishedInRange < 3) { $items[] = [ 'title' => 'Increase publishing cadence', 'body' => 'The calendar is still the clearest place to turn draft backlog into visible output.', 'href' => route('studio.calendar'), 'cta' => 'Plan schedule', ]; } if (count($preferences['featured_modules'] ?? []) < 3) { $items[] = [ 'title' => 'Expand featured module coverage', 'body' => 'Featured content gives your strongest modules a cleaner discovery path from the public profile.', 'href' => route('studio.featured'), 'cta' => 'Manage featured', ]; } if ($challengeEntries === 0) { $items[] = [ 'title' => 'Use challenges as a growth surface', 'body' => 'Challenge runs create another discovery path for cards beyond your normal publishing feed.', 'href' => route('studio.challenges'), 'cta' => 'Review challenges', ]; } $items[] = [ 'title' => 'Stay close to response signals', 'body' => 'Inbox and comments are still the fastest route from passive reach to actual creator retention.', 'href' => route('studio.inbox'), 'cta' => 'Open inbox', ]; return collect($items)->take(4)->values()->all(); } private function checkpoint(string $key, string $label, int $score, string $detail, string $href, string $cta): array { return [ 'key' => $key, 'label' => $label, 'score' => $score, 'status' => $score >= 80 ? 'strong' : ($score >= 55 ? 'building' : 'needs_attention'), 'detail' => $detail, 'href' => $href, 'cta' => $cta, ]; } private function milestone(string $key, string $label, int $current, int $target): array { return [ 'key' => $key, 'label' => $label, 'current' => $current, 'target' => $target, 'progress' => $target > 0 ? min(100, (int) round(($current / $target) * 100)) : 100, ]; } private function ratioScore(int $current, int $target): int { if ($target <= 0) { return 100; } return max(0, min(100, (int) round(($current / $target) * 100))); } private function nextMilestone(int $current, array $steps): int { foreach ($steps as $step) { if ($current < $step) { return $step; } } return max($current, 1); } private function profileScore(User $user, int $socialLinksCount): int { $score = 0; if (filled($user->name)) { $score += 15; } if (filled($user->profile?->description)) { $score += 20; } if (filled($user->profile?->about)) { $score += 20; } if (filled($user->profile?->website)) { $score += 15; } if (filled($user->profile?->avatar_url)) { $score += 10; } if (filled($user->cover_hash) && filled($user->cover_ext)) { $score += 10; } if ($socialLinksCount > 0) { $score += 10; } return min(100, $score); } }