224 lines
8.8 KiB
PHP
224 lines
8.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Studio;
|
|
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Collection;
|
|
|
|
final class CreatorStudioCalendarService
|
|
{
|
|
public function __construct(
|
|
private readonly CreatorStudioContentService $content,
|
|
private readonly CreatorStudioScheduledService $scheduled,
|
|
) {
|
|
}
|
|
|
|
public function build(User $user, array $filters = []): array
|
|
{
|
|
$view = $this->normalizeView((string) ($filters['view'] ?? 'month'));
|
|
$module = $this->normalizeModule((string) ($filters['module'] ?? 'all'));
|
|
$status = $this->normalizeStatus((string) ($filters['status'] ?? 'scheduled'));
|
|
$query = trim((string) ($filters['q'] ?? ''));
|
|
$focusDate = $this->normalizeFocusDate((string) ($filters['focus_date'] ?? ''));
|
|
|
|
$scheduledItems = $this->scheduledItems($user, $module, $query);
|
|
$unscheduledItems = $this->unscheduledItems($user, $module, $query);
|
|
|
|
return [
|
|
'filters' => [
|
|
'view' => $view,
|
|
'module' => $module,
|
|
'status' => $status,
|
|
'q' => $query,
|
|
'focus_date' => $focusDate->toDateString(),
|
|
],
|
|
'view_options' => [
|
|
['value' => 'month', 'label' => 'Month'],
|
|
['value' => 'week', 'label' => 'Week'],
|
|
['value' => 'agenda', 'label' => 'Agenda'],
|
|
],
|
|
'status_options' => [
|
|
['value' => 'scheduled', 'label' => 'Scheduled only'],
|
|
['value' => 'unscheduled', 'label' => 'Unscheduled queue'],
|
|
['value' => 'all', 'label' => 'Everything'],
|
|
],
|
|
'module_options' => array_merge([
|
|
['value' => 'all', 'label' => 'All content'],
|
|
], collect($this->content->moduleSummaries($user))->map(fn (array $summary): array => [
|
|
'value' => $summary['key'],
|
|
'label' => $summary['label'],
|
|
])->all()),
|
|
'summary' => $this->summary($scheduledItems, $unscheduledItems),
|
|
'month' => $this->monthGrid($scheduledItems, $focusDate),
|
|
'week' => $this->weekGrid($scheduledItems, $focusDate),
|
|
'agenda' => $this->agenda($scheduledItems),
|
|
'scheduled_items' => $status === 'unscheduled' ? [] : $scheduledItems->take(18)->values()->all(),
|
|
'unscheduled_items' => $status === 'scheduled' ? [] : $unscheduledItems->take(12)->values()->all(),
|
|
'gaps' => $this->gaps($scheduledItems, $focusDate),
|
|
];
|
|
}
|
|
|
|
private function scheduledItems(User $user, string $module, string $query): Collection
|
|
{
|
|
$items = $module === 'all'
|
|
? collect($this->content->providers())->flatMap(fn ($provider) => $provider->scheduledItems($user, 320))
|
|
: ($this->content->provider($module)?->scheduledItems($user, 320) ?? collect());
|
|
|
|
if ($query !== '') {
|
|
$needle = mb_strtolower($query);
|
|
$items = $items->filter(fn (array $item): bool => str_contains(mb_strtolower((string) ($item['title'] ?? '')), $needle));
|
|
}
|
|
|
|
return $items
|
|
->sortBy(fn (array $item): int => strtotime((string) ($item['scheduled_at'] ?? $item['published_at'] ?? '')) ?: PHP_INT_MAX)
|
|
->values();
|
|
}
|
|
|
|
private function unscheduledItems(User $user, string $module, string $query): Collection
|
|
{
|
|
$items = $module === 'all'
|
|
? collect($this->content->providers())->flatMap(fn ($provider) => $provider->items($user, 'all', 240))
|
|
: ($this->content->provider($module)?->items($user, 'all', 240) ?? collect());
|
|
|
|
return $items
|
|
->filter(function (array $item) use ($query): bool {
|
|
if (filled($item['scheduled_at'] ?? null)) {
|
|
return false;
|
|
}
|
|
|
|
if (in_array((string) ($item['status'] ?? ''), ['archived', 'hidden', 'rejected'], true)) {
|
|
return false;
|
|
}
|
|
|
|
if ($query === '') {
|
|
return true;
|
|
}
|
|
|
|
return str_contains(mb_strtolower((string) ($item['title'] ?? '')), mb_strtolower($query));
|
|
})
|
|
->sortByDesc(fn (array $item): int => strtotime((string) ($item['updated_at'] ?? $item['created_at'] ?? '')) ?: 0)
|
|
->values();
|
|
}
|
|
|
|
private function summary(Collection $scheduledItems, Collection $unscheduledItems): array
|
|
{
|
|
$days = $scheduledItems
|
|
->groupBy(fn (array $item): string => date('Y-m-d', strtotime((string) ($item['scheduled_at'] ?? $item['published_at'] ?? now()->toIso8601String()))))
|
|
->map(fn (Collection $items): int => $items->count());
|
|
|
|
return [
|
|
'scheduled_total' => $scheduledItems->count(),
|
|
'unscheduled_total' => $unscheduledItems->count(),
|
|
'overloaded_days' => $days->filter(fn (int $count): bool => $count >= 3)->count(),
|
|
'next_publish_at' => $scheduledItems->first()['scheduled_at'] ?? null,
|
|
];
|
|
}
|
|
|
|
private function monthGrid(Collection $scheduledItems, Carbon $focusDate): array
|
|
{
|
|
$start = $focusDate->copy()->startOfMonth()->startOfWeek();
|
|
$end = $focusDate->copy()->endOfMonth()->endOfWeek();
|
|
$days = [];
|
|
|
|
for ($date = $start->copy(); $date->lte($end); $date->addDay()) {
|
|
$key = $date->toDateString();
|
|
$items = $scheduledItems->filter(fn (array $item): bool => str_starts_with((string) ($item['scheduled_at'] ?? ''), $key))->values();
|
|
$days[] = [
|
|
'date' => $key,
|
|
'day' => $date->day,
|
|
'is_current_month' => $date->month === $focusDate->month,
|
|
'count' => $items->count(),
|
|
'items' => $items->take(3)->all(),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'label' => $focusDate->format('F Y'),
|
|
'days' => $days,
|
|
];
|
|
}
|
|
|
|
private function weekGrid(Collection $scheduledItems, Carbon $focusDate): array
|
|
{
|
|
$start = $focusDate->copy()->startOfWeek();
|
|
|
|
return [
|
|
'label' => $start->format('M j') . ' - ' . $start->copy()->endOfWeek()->format('M j'),
|
|
'days' => collect(range(0, 6))->map(function (int $offset) use ($start, $scheduledItems): array {
|
|
$date = $start->copy()->addDays($offset);
|
|
$key = $date->toDateString();
|
|
$items = $scheduledItems->filter(fn (array $item): bool => str_starts_with((string) ($item['scheduled_at'] ?? ''), $key))->values();
|
|
|
|
return [
|
|
'date' => $key,
|
|
'label' => $date->format('D j'),
|
|
'items' => $items->all(),
|
|
];
|
|
})->all(),
|
|
];
|
|
}
|
|
|
|
private function agenda(Collection $scheduledItems): array
|
|
{
|
|
return $scheduledItems
|
|
->groupBy(fn (array $item): string => date('Y-m-d', strtotime((string) ($item['scheduled_at'] ?? $item['published_at'] ?? now()->toIso8601String()))))
|
|
->map(fn (Collection $items, string $date): array => [
|
|
'date' => $date,
|
|
'label' => Carbon::parse($date)->format('M j'),
|
|
'count' => $items->count(),
|
|
'items' => $items->values()->all(),
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function gaps(Collection $scheduledItems, Carbon $focusDate): array
|
|
{
|
|
return collect(range(0, 13))
|
|
->map(function (int $offset) use ($focusDate, $scheduledItems): ?array {
|
|
$date = $focusDate->copy()->startOfDay()->addDays($offset);
|
|
$key = $date->toDateString();
|
|
$count = $scheduledItems->filter(fn (array $item): bool => str_starts_with((string) ($item['scheduled_at'] ?? ''), $key))->count();
|
|
|
|
if ($count > 0) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'date' => $key,
|
|
'label' => $date->format('D, M j'),
|
|
];
|
|
})
|
|
->filter()
|
|
->take(6)
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function normalizeView(string $view): string
|
|
{
|
|
return in_array($view, ['month', 'week', 'agenda'], true) ? $view : 'month';
|
|
}
|
|
|
|
private function normalizeStatus(string $status): string
|
|
{
|
|
return in_array($status, ['scheduled', 'unscheduled', 'all'], true) ? $status : 'scheduled';
|
|
}
|
|
|
|
private function normalizeModule(string $module): string
|
|
{
|
|
return in_array($module, ['all', 'artworks', 'cards', 'collections', 'stories'], true) ? $module : 'all';
|
|
}
|
|
|
|
private function normalizeFocusDate(string $value): Carbon
|
|
{
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1) {
|
|
return Carbon::parse($value);
|
|
}
|
|
|
|
return now();
|
|
}
|
|
} |