Files
hado/core/app/Http/Controllers/InboxController.php
2026-09-04 08:28:36 -03:00

110 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers;
use App\Enums\EventState;
use App\Http\Controllers\Api\EventsController as Presenter;
use App\Http\Controllers\Me\ChannelsController;
use App\Http\Controllers\Me\ProfileController;
use App\Models\Event;
use App\Models\User;
use App\Scheduling\DueResolver;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
class InboxController extends Controller
{
private const MONTHS = ['ЯНВ', 'ФЕВ', 'МАР', 'АПР', 'МАЙ', 'ИЮН', 'ИЮЛ', 'АВГ', 'СЕН', 'ОКТ', 'НОЯ', 'ДЕК'];
public function __construct(private readonly DueResolver $due) {}
public function __invoke(Request $request): View
{
/** @var User $user */
$user = $request->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<string,mixed> */
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)).' назад',
};
}
}