applyPreset('today'); } /** * Islands whose content depends on the global range; skipped islands never re-render on their own. * * @var array */ private const RANGE_DEPENDENT_ISLANDS = [ 'article-statistics', 'articles-trend', 'articles-per-feed', 'publications-per-channel', ]; public function applyPreset(string $preset): void { try { $range = DateRange::preset($preset); } catch (InvalidArgumentException) { return; } $this->applyRange($range->from->toDateString(), $range->to->toDateString()); } public function applyRange(string $from, string $to): void { $this->from = $from; $this->to = $to; $this->validateRange(); $this->refreshRangeDependentIslands(); } private function refreshRangeDependentIslands(): void { foreach (self::RANGE_DEPENDENT_ISLANDS as $island) { $this->renderIsland(name: $island); } } /** * @return array */ #[Computed] public function presets(): array { return DateRange::presets(); } public bool $rangeIsValid = true; public function range(): ?DateRange { $from = $this->parseBoundary($this->from); $to = $this->parseBoundary($this->to); if (! $from instanceof Carbon || ! $to instanceof Carbon) { return null; } try { return new DateRange($from->startOfDay(), $to->endOfDay()); } catch (InvalidArgumentException) { return null; } } private function parseBoundary(string $value): ?Carbon { try { return Carbon::parse($value); } catch (\Exception) { return null; } } /** * @return array */ #[Computed] public function articleStats(): array { $range = $this->range(); if (! $range instanceof DateRange) { return [ 'articles_fetched' => 0, 'articles_published' => 0, 'published_percentage' => 0.0, ]; } return app(DashboardStatsService::class)->getStats($range); } #[Computed] public function articlesTrend(): SeriesResult { $range = $this->range(); return $range instanceof DateRange ? app(ArticlesTrend::class)->for($range) : new SeriesResult([], []); } #[Computed] public function articlesPerFeed(): BreakdownResult { return $this->breakdown(ArticlesPerFeed::class); } #[Computed] public function publicationsPerChannel(): BreakdownResult { return $this->breakdown(PublicationsPerChannel::class); } /** * @param class-string $stat */ private function breakdown(string $stat): BreakdownResult { $range = $this->range(); return $range instanceof DateRange ? app($stat)->for($range) : new BreakdownResult([]); } /** * @return array */ #[Computed] public function systemStats(): array { return app(DashboardStatsService::class)->getSystemStats(); } public function updated(string $property): void { if (in_array($property, ['from', 'to'], true)) { $this->validateRange(); $this->refreshRangeDependentIslands(); } } private function validateRange(): void { $this->resetErrorBag(['from', 'to']); $from = $this->parseBoundary($this->from); $to = $this->parseBoundary($this->to); if (! $from instanceof Carbon) { $this->addError('from', 'The start date is not a valid date.'); } if (! $to instanceof Carbon) { $this->addError('to', 'The end date is not a valid date.'); } if ($from instanceof Carbon && $to instanceof Carbon && $to->lessThan($from)) { $this->addError('to', 'The end date must not be earlier than the start date.'); } $this->rangeIsValid = $this->range() instanceof DateRange; } public function render(): View { return view('livewire.dashboard')->layout('layouts.app'); } }