261 lines
9.7 KiB
PHP
261 lines
9.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Studio\Providers;
|
|
|
|
use App\Models\NovaCard;
|
|
use App\Models\User;
|
|
use App\Services\Studio\Contracts\CreatorStudioProvider;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
final class CardStudioProvider implements CreatorStudioProvider
|
|
{
|
|
public function key(): string
|
|
{
|
|
return 'cards';
|
|
}
|
|
|
|
public function label(): string
|
|
{
|
|
return 'Cards';
|
|
}
|
|
|
|
public function icon(): string
|
|
{
|
|
return 'fa-solid fa-id-card';
|
|
}
|
|
|
|
public function createUrl(): string
|
|
{
|
|
return route('studio.cards.create');
|
|
}
|
|
|
|
public function indexUrl(): string
|
|
{
|
|
return route('studio.cards.index');
|
|
}
|
|
|
|
public function summary(User $user): array
|
|
{
|
|
$baseQuery = NovaCard::query()->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;
|
|
}
|
|
} |