withTrashed()->where('user_id', $user->id); $count = (clone $baseQuery) ->whereNull('deleted_at') ->whereNotIn('status', [NovaCard::STATUS_HIDDEN, NovaCard::STATUS_REJECTED]) ->count(); $draftCount = (clone $baseQuery) ->whereNull('deleted_at') ->where('status', NovaCard::STATUS_DRAFT) ->count(); $publishedCount = (clone $baseQuery) ->whereNull('deleted_at') ->where('status', NovaCard::STATUS_PUBLISHED) ->count(); $recentPublishedCount = (clone $baseQuery) ->whereNull('deleted_at') ->where('status', NovaCard::STATUS_PUBLISHED) ->where('published_at', '>=', now()->subDays(30)) ->count(); $archivedCount = (clone $baseQuery) ->where(function (Builder $query): void { $query->whereNotNull('deleted_at') ->orWhereIn('status', [NovaCard::STATUS_HIDDEN, NovaCard::STATUS_REJECTED]); }) ->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 card', 'index_url' => $this->indexUrl(), 'create_url' => $this->createUrl(), ]; } public function items(User $user, string $bucket = 'all', int $limit = 200): Collection { $query = NovaCard::query() ->withTrashed() ->where('user_id', $user->id) ->with(['category', 'tags']) ->orderByDesc('updated_at') ->limit($limit); if ($bucket === 'drafts') { $query->whereNull('deleted_at')->where('status', NovaCard::STATUS_DRAFT); } elseif ($bucket === 'scheduled') { $query->whereNull('deleted_at')->where('status', NovaCard::STATUS_SCHEDULED); } elseif ($bucket === 'archived') { $query->where(function (Builder $builder): void { $builder->whereNotNull('deleted_at') ->orWhereIn('status', [NovaCard::STATUS_HIDDEN, NovaCard::STATUS_REJECTED]); }); } elseif ($bucket === 'published') { $query->whereNull('deleted_at')->where('status', NovaCard::STATUS_PUBLISHED); } else { $query->whereNull('deleted_at')->whereNotIn('status', [NovaCard::STATUS_HIDDEN, NovaCard::STATUS_REJECTED]); } return $query->get()->map(fn (NovaCard $card): array => $this->mapItem($card)); } public function topItems(User $user, int $limit = 5): Collection { return NovaCard::query() ->where('user_id', $user->id) ->whereNull('deleted_at') ->where('status', NovaCard::STATUS_PUBLISHED) ->orderByDesc('trending_score') ->orderByDesc('views_count') ->limit($limit) ->get() ->map(fn (NovaCard $card): array => $this->mapItem($card)); } public function analytics(User $user): array { $totals = NovaCard::query() ->where('user_id', $user->id) ->whereNull('deleted_at') ->selectRaw('COALESCE(SUM(views_count), 0) as views') ->selectRaw('COALESCE(SUM(likes_count + favorites_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): Collection { return $this->items($user, 'scheduled', $limit); } private function mapItem(NovaCard $card): array { $status = $card->deleted_at || in_array($card->status, [NovaCard::STATUS_HIDDEN, NovaCard::STATUS_REJECTED], true) ? 'archived' : $card->status; return [ 'id' => sprintf('%s:%d', $this->key(), (int) $card->id), 'numeric_id' => (int) $card->id, 'module' => $this->key(), 'module_label' => $this->label(), 'module_icon' => $this->icon(), 'title' => $card->title, 'subtitle' => $card->category?->name ?: strtoupper((string) $card->format), 'description' => $card->description, 'status' => $status, 'visibility' => $card->visibility, 'image_url' => $card->previewUrl(), 'preview_url' => route('studio.cards.preview', ['id' => $card->id]), 'view_url' => $card->status === NovaCard::STATUS_PUBLISHED ? $card->publicUrl() : route('studio.cards.preview', ['id' => $card->id]), 'edit_url' => route('studio.cards.edit', ['id' => $card->id]), 'manage_url' => route('studio.cards.edit', ['id' => $card->id]), 'analytics_url' => route('studio.cards.analytics', ['id' => $card->id]), 'create_url' => $this->createUrl(), 'actions' => $this->actionsFor($card, $status), 'created_at' => $card->created_at?->toIso8601String(), 'updated_at' => $card->updated_at?->toIso8601String(), 'published_at' => $card->published_at?->toIso8601String(), 'scheduled_at' => $card->scheduled_for?->toIso8601String(), 'schedule_timezone' => $card->scheduling_timezone, 'featured' => (bool) $card->featured, 'metrics' => [ 'views' => (int) $card->views_count, 'appreciation' => (int) ($card->likes_count + $card->favorites_count), 'shares' => (int) $card->shares_count, 'comments' => (int) $card->comments_count, 'saves' => (int) $card->saves_count, ], 'engagement_score' => (int) $card->views_count + ((int) $card->likes_count * 2) + ((int) $card->favorites_count * 2) + ((int) $card->comments_count * 3) + ((int) $card->shares_count * 2) + ((int) $card->saves_count * 2), 'taxonomies' => [ 'categories' => $card->category ? [[ 'id' => (int) $card->category->id, 'name' => (string) $card->category->name, 'slug' => (string) $card->category->slug, ]] : [], 'tags' => $card->tags->map(fn ($entry): array => [ 'id' => (int) $entry->id, 'name' => (string) $entry->name, 'slug' => (string) $entry->slug, ])->values()->all(), ], ]; } private function actionsFor(NovaCard $card, string $status): array { $actions = [ [ 'key' => 'duplicate', 'label' => 'Duplicate', 'icon' => 'fa-solid fa-id-card', 'type' => 'request', 'method' => 'post', 'url' => route('api.cards.duplicate', ['id' => $card->id]), 'redirect_pattern' => route('studio.cards.edit', ['id' => '__ID__']), ], ]; if ($status === NovaCard::STATUS_DRAFT) { $actions[] = [ 'key' => 'delete', 'label' => 'Delete draft', 'icon' => 'fa-solid fa-trash', 'type' => 'request', 'method' => 'delete', 'url' => route('api.cards.drafts.destroy', ['id' => $card->id]), 'confirm' => 'Delete this card draft?', ]; } if ($status === NovaCard::STATUS_SCHEDULED) { $actions[] = [ 'key' => 'publish_now', 'label' => 'Publish now', 'icon' => 'fa-solid fa-bolt', 'type' => 'request', 'method' => 'post', 'url' => route('api.studio.schedule.publishNow', ['module' => 'cards', 'id' => $card->id]), ]; $actions[] = [ 'key' => 'unschedule', 'label' => 'Unschedule', 'icon' => 'fa-solid fa-calendar-xmark', 'type' => 'request', 'method' => 'post', 'url' => route('api.studio.schedule.unschedule', ['module' => 'cards', 'id' => $card->id]), ]; } return $actions; } }