Improve creator studio browsing and versioning

This commit is contained in:
2026-04-16 15:01:15 +02:00
parent 56eaa3bcbf
commit cdd42a0186
12 changed files with 728 additions and 140 deletions

View File

@@ -53,6 +53,7 @@ final class CreatorStudioContentService
$bucket = $fixedBucket ?: $this->normalizeBucket((string) ($filters['bucket'] ?? 'all'));
$search = trim((string) ($filters['q'] ?? ''));
$sort = $this->normalizeSort((string) ($filters['sort'] ?? 'updated_desc'));
$contentType = (string) ($filters['content_type'] ?? 'all');
$category = (string) ($filters['category'] ?? 'all');
$tag = trim((string) ($filters['tag'] ?? ''));
$visibility = (string) ($filters['visibility'] ?? 'all');
@@ -63,7 +64,7 @@ final class CreatorStudioContentService
$items = $module === 'all'
? SupportCollection::make($this->providers())->flatMap(fn (CreatorStudioProvider $provider) => $provider->items($user, $this->providerBucket($bucket), 200))
: $this->provider($module)?->items($user, $this->providerBucket($bucket), 240) ?? SupportCollection::make();
: $this->provider($module)?->items($user, $this->providerBucket($bucket), 0) ?? SupportCollection::make();
if ($bucket === 'featured') {
$items = $items->filter(fn (array $item): bool => (bool) ($item['featured'] ?? false));
@@ -91,6 +92,19 @@ final class CreatorStudioContentService
});
}
$artworkFilterItems = null;
if ($module === 'artworks') {
$artworkFilterItems = $items->values();
}
if ($module === 'artworks' && $contentType !== 'all') {
$items = $items->filter(function (array $item) use ($contentType): bool {
return SupportCollection::make($item['taxonomies']['content_types'] ?? [])
->contains(fn (array $entry): bool => (string) ($entry['slug'] ?? '') === $contentType);
});
}
if ($module === 'artworks' && $category !== 'all') {
$items = $items->filter(function (array $item) use ($category): bool {
return SupportCollection::make($item['taxonomies']['categories'] ?? [])
@@ -141,6 +155,7 @@ final class CreatorStudioContentService
'bucket' => $bucket,
'q' => $search,
'sort' => $sort,
'content_type' => $contentType,
'category' => $category,
'tag' => $tag,
'visibility' => $visibility,
@@ -174,12 +189,13 @@ final class CreatorStudioContentService
['value' => 'title_asc', 'label' => 'Title A-Z'],
],
'advanced_filters' => $this->advancedFilters($module, $items, [
'content_type' => $contentType,
'category' => $category,
'tag' => $tag,
'visibility' => $visibility,
'activity_state' => $activityState,
'stale' => $stale,
]),
], $artworkFilterItems),
];
}
@@ -323,34 +339,65 @@ final class CreatorStudioContentService
/**
* @param array<string, string> $currentFilters
*/
private function advancedFilters(string $module, SupportCollection $items, array $currentFilters): array
private function advancedFilters(string $module, SupportCollection $items, array $currentFilters, ?SupportCollection $optionItems = null): array
{
return match ($module) {
'artworks' => [
[
'key' => 'category',
'label' => 'Category',
'type' => 'select',
'value' => $currentFilters['category'] ?? 'all',
'options' => array_merge([
['value' => 'all', 'label' => 'All categories'],
], $items
->flatMap(fn (array $item) => $item['taxonomies']['categories'] ?? [])
->unique('slug')
->sortBy('name')
->map(fn (array $entry): array => [
'value' => (string) ($entry['slug'] ?? ''),
'label' => (string) ($entry['name'] ?? 'Category'),
])->values()->all()),
],
[
'key' => 'tag',
'label' => 'Tag',
'type' => 'search',
'value' => $currentFilters['tag'] ?? '',
'placeholder' => 'Filter by tag',
],
],
'artworks' => (function () use ($items, $currentFilters, $optionItems): array {
$optionItems = $optionItems ?? $items;
$selectedContentType = $currentFilters['content_type'] ?? 'all';
$contentTypeOptions = array_merge([
['value' => 'all', 'label' => 'All content types'],
], $optionItems
->flatMap(fn (array $item) => $item['taxonomies']['content_types'] ?? [])
->unique('slug')
->sortBy('name')
->map(fn (array $entry): array => [
'value' => (string) ($entry['slug'] ?? ''),
'label' => (string) ($entry['name'] ?? 'Content type'),
])->values()->all());
$categoryItems = $selectedContentType === 'all'
? $optionItems
: $optionItems->filter(function (array $item) use ($selectedContentType): bool {
return SupportCollection::make($item['taxonomies']['content_types'] ?? [])
->contains(fn (array $entry): bool => (string) ($entry['slug'] ?? '') === $selectedContentType);
});
return [
[
'key' => 'content_type',
'label' => 'Content Type',
'type' => 'select',
'value' => $currentFilters['content_type'] ?? 'all',
'options' => $contentTypeOptions,
],
[
'key' => 'category',
'label' => 'Category',
'type' => 'select',
'value' => $currentFilters['category'] ?? 'all',
'options' => array_merge([
['value' => 'all', 'label' => 'All categories', 'content_type_slug' => 'all'],
], $categoryItems
->flatMap(fn (array $item) => $item['taxonomies']['categories'] ?? [])
->unique('slug')
->sortBy('name')
->map(fn (array $entry): array => [
'value' => (string) ($entry['slug'] ?? ''),
'label' => (string) ($entry['name'] ?? 'Category'),
'content_type_slug' => (string) ($entry['content_type_slug'] ?? 'all'),
])->values()->all()),
],
[
'key' => 'tag',
'label' => 'Tag',
'type' => 'search',
'value' => $currentFilters['tag'] ?? '',
'placeholder' => 'Filter by tag',
],
];
})(),
'collections' => [[
'key' => 'visibility',
'label' => 'Visibility',

View File

@@ -7,6 +7,7 @@ namespace App\Services\Studio\Providers;
use App\Models\Artwork;
use App\Models\ArtworkStats;
use App\Models\User;
use App\Services\Artworks\ArtworkPublicationService;
use App\Services\Studio\Contracts\CreatorStudioProvider;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
@@ -14,6 +15,10 @@ use Illuminate\Support\Facades\DB;
final class ArtworkStudioProvider implements CreatorStudioProvider
{
public function __construct(
private readonly ArtworkPublicationService $publicationService,
) {}
public function key(): string
{
return 'artworks';
@@ -41,6 +46,8 @@ final class ArtworkStudioProvider implements CreatorStudioProvider
public function summary(User $user): array
{
$this->publicationService->publishDueScheduledForUser((int) $user->id);
$baseQuery = Artwork::query()->withTrashed()->where('user_id', $user->id);
$count = (clone $baseQuery)
@@ -90,12 +97,15 @@ final class ArtworkStudioProvider implements CreatorStudioProvider
public function items(User $user, string $bucket = 'all', int $limit = 200): Collection
{
$this->publicationService->publishDueScheduledForUser((int) $user->id);
$query = Artwork::query()
->withTrashed()
->where('user_id', $user->id)
->with([
'stats',
'categories',
'categories.contentType',
'tags',
'features' => function ($query): void {
$query->where('is_active', true)
@@ -106,8 +116,11 @@ final class ArtworkStudioProvider implements CreatorStudioProvider
});
},
])
->orderByDesc('updated_at')
->limit($limit);
->orderByDesc('updated_at');
if ($limit > 0) {
$query->limit($limit);
}
if ($bucket === 'drafts') {
$query->whereNull('deleted_at')
@@ -134,6 +147,8 @@ final class ArtworkStudioProvider implements CreatorStudioProvider
public function topItems(User $user, int $limit = 5): Collection
{
$this->publicationService->publishDueScheduledForUser((int) $user->id);
return Artwork::query()
->where('user_id', $user->id)
->whereNull('deleted_at')
@@ -226,10 +241,21 @@ final class ArtworkStudioProvider implements CreatorStudioProvider
+ ((int) ($stats?->comments_count ?? 0) * 3)
+ ((int) ($stats?->shares_count ?? 0) * 2),
'taxonomies' => [
'content_types' => $artwork->categories
->map(fn ($entry) => $entry->contentType)
->filter()
->unique('id')
->map(fn ($entry): array => [
'id' => (int) $entry->id,
'name' => (string) $entry->name,
'slug' => (string) $entry->slug,
])->values()->all(),
'categories' => $artwork->categories->map(fn ($entry): array => [
'id' => (int) $entry->id,
'name' => (string) $entry->name,
'slug' => (string) $entry->slug,
'content_type' => (string) ($entry->contentType?->name ?? ''),
'content_type_slug' => (string) ($entry->contentType?->slug ?? ''),
])->values()->all(),
'tags' => $artwork->tags->map(fn ($entry): array => [
'id' => (int) $entry->id,

View File

@@ -92,8 +92,11 @@ final class CardStudioProvider implements CreatorStudioProvider
->withTrashed()
->where('user_id', $user->id)
->with(['category', 'tags'])
->orderByDesc('updated_at')
->limit($limit);
->orderByDesc('updated_at');
if ($limit > 0) {
$query->limit($limit);
}
if ($bucket === 'drafts') {
$query->whereNull('deleted_at')->where('status', NovaCard::STATUS_DRAFT);

View File

@@ -101,8 +101,11 @@ final class CollectionStudioProvider implements CreatorStudioProvider
->withTrashed()
->where('user_id', $user->id)
->with(['user.profile', 'coverArtwork'])
->orderByDesc('updated_at')
->limit($limit);
->orderByDesc('updated_at');
if ($limit > 0) {
$query->limit($limit);
}
if ($bucket === 'drafts') {
$query->whereNull('deleted_at')

View File

@@ -83,8 +83,11 @@ final class StoryStudioProvider implements CreatorStudioProvider
$query = Story::query()
->where('creator_id', $user->id)
->with(['tags'])
->orderByDesc('updated_at')
->limit($limit);
->orderByDesc('updated_at');
if ($limit > 0) {
$query->limit($limit);
}
if ($bucket === 'drafts') {
$query->whereIn('status', ['draft', 'pending_review', 'rejected']);