analytics->overview($user); $moduleSummaries = $this->content->moduleSummaries($user); $preferences = $this->preferences->forUser($user); $challengeData = $this->challenges->build($user); $growthData = $this->growth->build($user, $preferences['analytics_range_days']); $featuredContent = $this->content->selectedItems($user, $preferences['featured_content']); return [ 'kpis' => [ 'total_content' => $analytics['totals']['content_count'], 'views_30d' => $analytics['totals']['views'], 'appreciation_30d' => $analytics['totals']['appreciation'], 'shares_30d' => $analytics['totals']['shares'], 'comments_30d' => $analytics['totals']['comments'], 'followers' => $analytics['totals']['followers'], ], 'module_summaries' => $moduleSummaries, 'quick_create' => $this->content->quickCreate(), 'continue_working' => $this->content->continueWorking($user, $preferences['draft_behavior']), 'scheduled_items' => $this->scheduled->upcoming($user, 5), 'recent_activity' => $this->activity->recent($user, 6), 'top_performers' => $analytics['top_content'], 'recent_comments' => $this->recentComments($user), 'recent_followers' => $this->recentFollowers($user), 'draft_reminders' => $this->content->draftReminders($user), 'stale_drafts' => $this->content->staleDrafts($user), 'recent_publishes' => $this->content->recentPublished($user, 6), 'growth_hints' => $this->growthHints($user, $moduleSummaries), 'active_challenges' => [ 'summary' => $challengeData['summary'], 'spotlight' => $challengeData['spotlight'], 'items' => collect($challengeData['active_challenges'] ?? [])->take(3)->values()->all(), ], 'creator_health' => [ 'score' => (int) round(collect($growthData['checkpoints'] ?? [])->avg('score') ?? 0), 'summary' => $growthData['summary'], 'checkpoints' => collect($growthData['checkpoints'] ?? [])->take(3)->values()->all(), ], 'featured_status' => $this->featuredStatus($preferences, $featuredContent), 'workflow_focus' => $this->workflowFocus($user), 'command_center' => $this->commandCenter($user), 'insight_blocks' => $analytics['insight_blocks'] ?? [], 'preferences' => [ 'widget_visibility' => $preferences['widget_visibility'], 'widget_order' => $preferences['widget_order'], 'card_density' => $preferences['card_density'], ], ]; } private function featuredStatus(array $preferences, array $featuredContent): array { $modules = ['artworks', 'cards', 'collections', 'stories']; $selectedModules = array_values(array_filter($modules, fn (string $module): bool => isset($featuredContent[$module]) && is_array($featuredContent[$module]))); $missingModules = array_values(array_diff($modules, $selectedModules)); return [ 'selected_count' => count($selectedModules), 'target_count' => count($modules), 'featured_modules' => $preferences['featured_modules'], 'missing_modules' => $missingModules, 'items' => collect($featuredContent) ->filter(fn ($item): bool => is_array($item)) ->values() ->take(4) ->all(), ]; } private function workflowFocus(User $user): array { $continue = collect($this->content->continueWorking($user, 'resume-last', 6)); return [ 'priority_drafts' => $continue ->filter(fn (array $item): bool => (bool) ($item['workflow']['is_stale_draft'] ?? false) || ! (bool) ($item['workflow']['readiness']['can_publish'] ?? false)) ->take(3) ->values() ->all(), 'ready_to_schedule' => $continue ->filter(fn (array $item): bool => (bool) ($item['workflow']['readiness']['can_publish'] ?? false)) ->take(3) ->values() ->all(), ]; } private function commandCenter(User $user): array { $scheduled = collect($this->scheduled->upcoming($user, 16)); $inbox = collect($this->activity->recent($user, 16)); $todayStart = now()->startOfDay(); $todayEnd = now()->endOfDay(); return [ 'publishing_today' => $scheduled->filter(function (array $item) use ($todayStart, $todayEnd): bool { $timestamp = strtotime((string) ($item['scheduled_at'] ?? $item['published_at'] ?? '')) ?: 0; return $timestamp >= $todayStart->getTimestamp() && $timestamp <= $todayEnd->getTimestamp(); })->values()->all(), 'attention_now' => $inbox->filter(fn (array $item): bool => in_array((string) ($item['type'] ?? ''), ['comment', 'notification'], true))->take(4)->values()->all(), ]; } private function growthHints(User $user, array $moduleSummaries): array { $user->loadMissing('profile'); $summaries = collect($moduleSummaries)->keyBy('key'); $hints = []; if (blank($user->profile?->bio) || blank($user->profile?->tagline)) { $hints[] = [ 'title' => 'Complete your creator profile', 'body' => 'Add a tagline and bio so your public presence matches the work you are publishing.', 'url' => route('studio.profile'), 'label' => 'Update profile', ]; } if (((int) ($summaries->get('cards')['count'] ?? 0)) === 0) { $hints[] = [ 'title' => 'Publish your first card', 'body' => 'Cards now live inside Creator Studio, making short-form publishing a first-class workflow.', 'url' => route('studio.cards.create'), 'label' => 'Create card', ]; } if (((int) ($summaries->get('collections')['count'] ?? 0)) === 0) { $hints[] = [ 'title' => 'Create a featured collection', 'body' => 'Curated collections give your profile a stronger editorial shape and a better publishing shelf.', 'url' => route('settings.collections.create'), 'label' => 'Start collection', ]; } if (((int) ($summaries->get('artworks')['count'] ?? 0)) === 0) { $hints[] = [ 'title' => 'Upload your first artwork', 'body' => 'Seed the workspace with a first long-form piece so analytics, drafts, and collections have something to build on.', 'url' => '/upload', 'label' => 'Upload artwork', ]; } return collect($hints)->take(3)->values()->all(); } public function recentComments(User $user, int $limit = 12): array { $artworkComments = DB::table('artwork_comments') ->join('artworks', 'artworks.id', '=', 'artwork_comments.artwork_id') ->join('users', 'users.id', '=', 'artwork_comments.user_id') ->where('artworks.user_id', $user->id) ->whereNull('artwork_comments.deleted_at') ->select([ 'artwork_comments.id', 'artwork_comments.content as body', 'artwork_comments.created_at', 'users.name as author_name', 'artworks.title as item_title', 'artworks.slug as item_slug', 'artworks.id as item_id', ]) ->orderByDesc('artwork_comments.created_at') ->limit($limit) ->get() ->map(fn ($row): array => [ 'id' => sprintf('artworks:%d', (int) $row->id), 'module' => 'artworks', 'module_label' => 'Artworks', 'author_name' => $row->author_name, 'item_title' => $row->item_title, 'body' => $row->body, 'created_at' => $this->normalizeDate($row->created_at), 'context_url' => route('art.show', ['id' => $row->item_id, 'slug' => $row->item_slug]), ]); $cardComments = NovaCardComment::query() ->with(['user:id,name,username', 'card:id,title,slug']) ->whereNull('deleted_at') ->whereHas('card', fn ($query) => $query->where('user_id', $user->id)) ->latest('created_at') ->limit($limit) ->get() ->map(fn (NovaCardComment $comment): array => [ 'id' => sprintf('cards:%d', (int) $comment->id), 'module' => 'cards', 'module_label' => 'Cards', 'author_name' => $comment->user?->name ?: $comment->user?->username ?: 'Creator', 'item_title' => $comment->card?->title, 'body' => $comment->body, 'created_at' => $comment->created_at?->toIso8601String(), 'context_url' => $comment->card ? route('studio.cards.analytics', ['id' => $comment->card->id]) : route('studio.cards.index'), ]); $collectionComments = CollectionComment::query() ->with(['user:id,name,username', 'collection:id,title']) ->whereNull('deleted_at') ->whereHas('collection', fn ($query) => $query->where('user_id', $user->id)) ->latest('created_at') ->limit($limit) ->get() ->map(fn (CollectionComment $comment): array => [ 'id' => sprintf('collections:%d', (int) $comment->id), 'module' => 'collections', 'module_label' => 'Collections', 'author_name' => $comment->user?->name ?: $comment->user?->username ?: 'Creator', 'item_title' => $comment->collection?->title, 'body' => $comment->body, 'created_at' => $comment->created_at?->toIso8601String(), 'context_url' => $comment->collection ? route('settings.collections.show', ['collection' => $comment->collection->id]) : route('studio.collections'), ]); $storyComments = StoryComment::query() ->with(['user:id,name,username', 'story:id,title']) ->whereNull('deleted_at') ->where('is_approved', true) ->whereHas('story', fn ($query) => $query->where('creator_id', $user->id)) ->latest('created_at') ->limit($limit) ->get() ->map(fn (StoryComment $comment): array => [ 'id' => sprintf('stories:%d', (int) $comment->id), 'module' => 'stories', 'module_label' => 'Stories', 'author_name' => $comment->user?->name ?: $comment->user?->username ?: 'Creator', 'item_title' => $comment->story?->title, 'body' => $comment->content, 'created_at' => $comment->created_at?->toIso8601String(), 'context_url' => $comment->story ? route('creator.stories.analytics', ['story' => $comment->story->id]) : route('studio.stories'), ]); return $artworkComments ->concat($cardComments) ->concat($collectionComments) ->concat($storyComments) ->sortByDesc(fn (array $comment): int => $this->timestamp($comment['created_at'] ?? null)) ->take($limit) ->values() ->all(); } public function recentFollowers(User $user, int $limit = 8): array { return DB::table('user_followers as uf') ->join('users as follower', 'follower.id', '=', 'uf.follower_id') ->leftJoin('user_profiles as profile', 'profile.user_id', '=', 'follower.id') ->where('uf.user_id', $user->id) ->whereNull('follower.deleted_at') ->orderByDesc('uf.created_at') ->limit($limit) ->get([ 'follower.id', 'follower.username', 'follower.name', 'profile.avatar_hash', 'uf.created_at', ]) ->map(fn ($row): array => [ 'id' => (int) $row->id, 'name' => $row->name ?: '@' . $row->username, 'username' => $row->username, 'profile_url' => '/@' . strtolower((string) $row->username), 'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 64), 'created_at' => $this->normalizeDate($row->created_at), ]) ->values() ->all(); } private function normalizeDate(mixed $value): ?string { if ($value instanceof \DateTimeInterface) { return $value->format(DATE_ATOM); } if (is_string($value) && $value !== '') { return $value; } return null; } private function timestamp(mixed $value): int { if (! is_string($value) || $value === '') { return 0; } return strtotime($value) ?: 0; } }