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(); } }