withTrashed()->where('user_id', $user->id); $count = (clone $baseQuery) ->whereNull('deleted_at') ->where('lifecycle_state', '!=', Collection::LIFECYCLE_ARCHIVED) ->count(); $draftCount = (clone $baseQuery) ->whereNull('deleted_at') ->where(function (Builder $query): void { $query->where('lifecycle_state', Collection::LIFECYCLE_DRAFT) ->orWhere('workflow_state', Collection::WORKFLOW_DRAFT) ->orWhere('workflow_state', Collection::WORKFLOW_IN_REVIEW); }) ->count(); $publishedCount = (clone $baseQuery) ->whereNull('deleted_at') ->whereIn('lifecycle_state', [Collection::LIFECYCLE_PUBLISHED, Collection::LIFECYCLE_FEATURED, Collection::LIFECYCLE_SCHEDULED]) ->count(); $recentPublishedCount = (clone $baseQuery) ->whereNull('deleted_at') ->whereIn('lifecycle_state', [Collection::LIFECYCLE_PUBLISHED, Collection::LIFECYCLE_FEATURED, Collection::LIFECYCLE_SCHEDULED]) ->where('published_at', '>=', now()->subDays(30)) ->count(); $archivedCount = (clone $baseQuery) ->where(function (Builder $query): void { $query->whereNotNull('deleted_at') ->orWhere('lifecycle_state', Collection::LIFECYCLE_ARCHIVED); }) ->count(); return [ 'key' => $this->key(), 'label' => $this->label(), 'icon' => $this->icon(), 'count' => $count, 'draft_count' => $draftCount, 'published_count' => $publishedCount, 'archived_count' => $archivedCount, 'trend_value' => $recentPublishedCount, 'trend_label' => 'published in 30d', 'quick_action_label' => 'Create collection', 'index_url' => $this->indexUrl(), 'create_url' => $this->createUrl(), ]; } public function items(User $user, string $bucket = 'all', int $limit = 200): SupportCollection { $query = Collection::query() ->withTrashed() ->where('user_id', $user->id) ->with(['user.profile', 'coverArtwork']) ->orderByDesc('updated_at') ->limit($limit); if ($bucket === 'drafts') { $query->whereNull('deleted_at') ->where(function (Builder $builder): void { $builder->where('lifecycle_state', Collection::LIFECYCLE_DRAFT) ->orWhere('workflow_state', Collection::WORKFLOW_DRAFT) ->orWhere('workflow_state', Collection::WORKFLOW_IN_REVIEW); }); } elseif ($bucket === 'scheduled') { $query->whereNull('deleted_at') ->where('lifecycle_state', Collection::LIFECYCLE_SCHEDULED); } elseif ($bucket === 'archived') { $query->where(function (Builder $builder): void { $builder->whereNotNull('deleted_at') ->orWhere('lifecycle_state', Collection::LIFECYCLE_ARCHIVED); }); } elseif ($bucket === 'published') { $query->whereNull('deleted_at') ->whereIn('lifecycle_state', [Collection::LIFECYCLE_PUBLISHED, Collection::LIFECYCLE_FEATURED, Collection::LIFECYCLE_SCHEDULED]); } else { $query->whereNull('deleted_at')->where('lifecycle_state', '!=', Collection::LIFECYCLE_ARCHIVED); } return collect($this->collections->mapCollectionCardPayloads($query->get(), true, $user)) ->map(fn (array $item): array => $this->mapItem($item)); } public function topItems(User $user, int $limit = 5): SupportCollection { $collections = Collection::query() ->where('user_id', $user->id) ->whereNull('deleted_at') ->whereIn('lifecycle_state', [Collection::LIFECYCLE_PUBLISHED, Collection::LIFECYCLE_FEATURED]) ->with(['user.profile', 'coverArtwork']) ->orderByDesc('ranking_score') ->orderByDesc('views_count') ->limit($limit) ->get(); return collect($this->collections->mapCollectionCardPayloads($collections, true, $user)) ->map(fn (array $item): array => $this->mapItem($item)); } public function analytics(User $user): array { $totals = Collection::query() ->where('user_id', $user->id) ->whereNull('deleted_at') ->selectRaw('COALESCE(SUM(views_count), 0) as views') ->selectRaw('COALESCE(SUM(likes_count + followers_count), 0) as appreciation') ->selectRaw('COALESCE(SUM(shares_count), 0) as shares') ->selectRaw('COALESCE(SUM(comments_count), 0) as comments') ->selectRaw('COALESCE(SUM(saves_count), 0) as saves') ->first(); return [ 'views' => (int) ($totals->views ?? 0), 'appreciation' => (int) ($totals->appreciation ?? 0), 'shares' => (int) ($totals->shares ?? 0), 'comments' => (int) ($totals->comments ?? 0), 'saves' => (int) ($totals->saves ?? 0), ]; } public function scheduledItems(User $user, int $limit = 50): SupportCollection { return $this->items($user, 'scheduled', $limit); } private function mapItem(array $item): array { $status = $item['lifecycle_state'] ?? 'draft'; if ($status === Collection::LIFECYCLE_FEATURED) { $status = 'published'; } return [ 'id' => sprintf('%s:%d', $this->key(), (int) $item['id']), 'numeric_id' => (int) $item['id'], 'module' => $this->key(), 'module_label' => $this->label(), 'module_icon' => $this->icon(), 'title' => $item['title'], 'subtitle' => $item['subtitle'] ?: ($item['type'] ? ucfirst((string) $item['type']) : null), 'description' => $item['summary'] ?: $item['description'], 'status' => $status, 'visibility' => $item['visibility'], 'image_url' => $item['cover_image'], 'preview_url' => $item['url'], 'view_url' => $item['url'], 'edit_url' => $item['edit_url'] ?: $item['manage_url'], 'manage_url' => $item['manage_url'], 'analytics_url' => route('settings.collections.analytics', ['collection' => $item['id']]), 'create_url' => $this->createUrl(), 'actions' => $this->actionsFor($item, $status), 'created_at' => $item['published_at'] ?? $item['updated_at'], 'updated_at' => $item['updated_at'], 'published_at' => $item['published_at'] ?? null, 'scheduled_at' => $status === Collection::LIFECYCLE_SCHEDULED ? ($item['published_at'] ?? null) : null, 'featured' => (bool) ($item['is_featured'] ?? false), 'metrics' => [ 'views' => (int) ($item['views_count'] ?? 0), 'appreciation' => (int) (($item['likes_count'] ?? 0) + ($item['followers_count'] ?? 0)), 'shares' => (int) ($item['shares_count'] ?? 0), 'comments' => (int) ($item['comments_count'] ?? 0), 'saves' => (int) ($item['saves_count'] ?? 0), ], 'engagement_score' => (int) ($item['views_count'] ?? 0) + ((int) ($item['likes_count'] ?? 0) * 2) + ((int) ($item['followers_count'] ?? 0) * 2) + ((int) ($item['comments_count'] ?? 0) * 3) + ((int) ($item['shares_count'] ?? 0) * 2) + ((int) ($item['saves_count'] ?? 0) * 2), 'taxonomies' => [ 'categories' => [], 'tags' => [], ], ]; } private function actionsFor(array $item, string $status): array { $collectionId = (int) $item['id']; $actions = []; $featured = (bool) ($item['is_featured'] ?? false); if ($status === 'draft') { $actions[] = $this->requestAction( 'publish', 'Publish', 'fa-solid fa-rocket', route('settings.collections.lifecycle', ['collection' => $collectionId]), [ 'lifecycle_state' => Collection::LIFECYCLE_PUBLISHED, 'visibility' => Collection::VISIBILITY_PUBLIC, 'published_at' => now()->toIso8601String(), ] ); } if (in_array($status, ['published', 'scheduled'], true)) { $actions[] = $this->requestAction( 'archive', 'Archive', 'fa-solid fa-box-archive', route('settings.collections.lifecycle', ['collection' => $collectionId]), [ 'lifecycle_state' => Collection::LIFECYCLE_ARCHIVED, 'archived_at' => now()->toIso8601String(), ] ); $actions[] = $featured ? $this->requestAction('unfeature', 'Remove feature', 'fa-solid fa-star-half-stroke', route('settings.collections.unfeature', ['collection' => $collectionId]), [], null, 'delete') : $this->requestAction('feature', 'Feature', 'fa-solid fa-star', route('settings.collections.feature', ['collection' => $collectionId]), []); if ($status === 'scheduled') { $actions[] = $this->requestAction('publish_now', 'Publish now', 'fa-solid fa-bolt', route('api.studio.schedule.publishNow', ['module' => 'collections', 'id' => $collectionId]), []); $actions[] = $this->requestAction('unschedule', 'Unschedule', 'fa-solid fa-calendar-xmark', route('api.studio.schedule.unschedule', ['module' => 'collections', 'id' => $collectionId]), []); } } if ($status === 'archived') { $actions[] = $this->requestAction( 'restore', 'Restore', 'fa-solid fa-rotate-left', route('settings.collections.lifecycle', ['collection' => $collectionId]), [ 'lifecycle_state' => Collection::LIFECYCLE_DRAFT, 'visibility' => Collection::VISIBILITY_PRIVATE, 'archived_at' => null, ] ); } $actions[] = $this->requestAction( 'delete', 'Delete', 'fa-solid fa-trash', route('settings.collections.destroy', ['collection' => $collectionId]), [], 'Delete this collection permanently?', 'delete' ); return $actions; } private function requestAction(string $key, string $label, string $icon, string $url, array $payload = [], ?string $confirm = null, string $method = 'post'): array { return [ 'key' => $key, 'label' => $label, 'icon' => $icon, 'type' => 'request', 'method' => $method, 'url' => $url, 'payload' => $payload, 'confirm' => $confirm, ]; } }