attributes->get('user'); $today = $user->now()->startOfDay(); $live = $user->events()->with('source') ->whereIn('state', EventState::nonTerminalValues()) ->get() ->map(fn (Event $e) => $this->card($e, $user, $today)) ->sortBy('sort')->values(); $groups = []; foreach (['today' => ['今日', 'СЕГОДНЯ'], 'overdue' => ['遅延', 'ПРОСРОЧЕНО'], 'preparing' => ['近日', 'ГРЯДЁТ'], 'scheduled' => ['予定', 'ЗАПЛАНИРОВАНО']] as $state => [$jp, $ru]) { $groups[] = ['state' => $state, 'jp' => $jp, 'ru' => $ru, 'events' => $live->where('state', $state)->values()->all()]; } $archive = $user->events()->with('source') ->whereIn('state', [EventState::Done->value, EventState::Expired->value]) ->orderByDesc('updated_at')->limit(100)->get() ->map(fn (Event $e) => [ 'title' => $e->payload['title'], 'source' => self::sourceLabel($e->source->name), 'due' => Presenter::present($e)['due_at'], 'closed_as' => $e->state === EventState::Expired ? 'Истекло' : ($e->payload['done_label'] ?? 'Сделано'), ])->values()->all(); $profile = ProfileController::present($user); return view('inbox', [ 'profile' => $profile, 'title' => $profile['badge'] > 0 ? "({$profile['badge']}) Hado" : 'Hado', 'groups' => $groups, 'archive' => $archive, 'channels' => $user->channels()->orderBy('id')->get()->map(ChannelsController::present(...))->values()->all(), 'timezones' => \DateTimeZone::listIdentifiers(), ]); } /** @return array */ private function card(Event $e, User $user, CarbonImmutable $today): array { $due = $this->due->resolve($e, $user); $wholeDay = $due->moment->eq($due->day->addDay()); $diff = (int) round($today->diffInDays($due->day)); return [ 'id' => $e->id, 'state' => $e->state->value, 'title' => $e->payload['title'], 'subtitle' => $e->payload['subtitle'] ?? null, 'link' => self::safeLink($e->payload['deep_link'] ?? null), 'done_label' => $e->payload['done_label'] ?? 'Сделано', 'can_remind' => $e->state !== EventState::Scheduled, 'source' => self::sourceLabel($e->source->name), 'due' => Presenter::present($e)['due_at'], 'due_month' => self::MONTHS[(int) $due->day->format('n') - 1], 'due_day' => $due->day->format('d'), 'due_time' => $wholeDay ? null : $due->moment->format('H:i'), 'rel' => self::relative($diff), 'sort' => $due->moment->getTimestamp(), ]; } public static function sourceLabel(string $name): string { return $name === 'manual' ? 'вручную' : $name; } /** Пропускает только относительные пути и http(s)-ссылки; прочее (напр. javascript:) отбрасывает. */ public static function safeLink(?string $link): ?string { return $link !== null && preg_match('#^(/|https?://)#i', $link) === 1 ? $link : null; } public static function relative(int $diff): string { $days = fn (int $n) => $n % 10 === 1 && $n % 100 !== 11 ? 'день' : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 12 || $n % 100 > 14) ? 'дня' : 'дней'); return match (true) { $diff === 0 => 'сегодня', $diff === 1 => 'завтра', $diff > 1 => "через {$diff} ".$days($diff), $diff === -1 => 'вчера', default => abs($diff).' '.$days(abs($diff)).' назад', }; } }