tenantId(); $query = TodayIssue::query() ->where('tenant_id', $tenantId) ->active() // 만료되지 않은 이슈만 ->orderByDesc('created_at'); // 뱃지 필터 if ($badge !== null && $badge !== 'all') { $query->byBadge($badge); } // 전체 개수 (필터 적용 전) $totalQuery = TodayIssue::query() ->where('tenant_id', $tenantId) ->active(); $totalCount = $totalQuery->count(); // 결과 조회 $issues = $query->limit($limit)->get(); $items = $issues->map(function (TodayIssue $issue) { return [ 'id' => $issue->source_type.'_'.$issue->source_id, 'badge' => $issue->badge, 'content' => $issue->content, 'time' => $this->formatRelativeTime($issue->created_at), 'date' => $issue->created_at?->toDateString(), 'needsApproval' => $issue->needs_approval, 'path' => $issue->path, ]; })->toArray(); return [ 'items' => $items, 'total_count' => $totalCount, ]; } /** * 읽지 않은 이슈 목록 조회 (헤더 알림용) * * @param int $limit 조회할 최대 항목 수 (기본 10) */ public function getUnreadList(int $limit = 10): array { $tenantId = $this->tenantId(); $issues = TodayIssue::query() ->where('tenant_id', $tenantId) ->unread() ->active() ->orderByDesc('created_at') ->limit($limit) ->get(); $totalCount = TodayIssue::query() ->where('tenant_id', $tenantId) ->unread() ->active() ->count(); $items = $issues->map(function (TodayIssue $issue) { return [ 'id' => $issue->id, 'badge' => $issue->badge, 'notification_type' => $issue->notification_type, 'content' => $issue->content, 'path' => $issue->path, 'needs_approval' => $issue->needs_approval, 'time' => $this->formatRelativeTime($issue->created_at), 'created_at' => $issue->created_at?->toIso8601String(), ]; })->toArray(); return [ 'items' => $items, 'total' => $totalCount, ]; } /** * 읽지 않은 이슈 개수 조회 (헤더 알림 뱃지용) */ public function getUnreadCount(): int { $tenantId = $this->tenantId(); return TodayIssue::query() ->where('tenant_id', $tenantId) ->unread() ->active() ->count(); } /** * 이슈 확인 처리 */ public function markAsRead(int $issueId): bool { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $issue = TodayIssue::where('tenant_id', $tenantId) ->where('id', $issueId) ->first(); if (! $issue) { return false; } return $issue->markAsRead($userId); } /** * 모든 이슈 읽음 처리 */ public function markAllAsRead(): int { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return TodayIssue::query() ->where('tenant_id', $tenantId) ->unread() ->active() ->update([ 'is_read' => true, 'read_by' => $userId, 'read_at' => now(), ]); } /** * 이슈 삭제 (확인 완료 처리) */ public function dismiss(string $sourceType, int $sourceId): bool { $tenantId = $this->tenantId(); return TodayIssue::removeBySource($tenantId, $sourceType, $sourceId); } /** * 뱃지별 개수 조회 */ public function countByBadge(): array { $tenantId = $this->tenantId(); $counts = TodayIssue::query() ->where('tenant_id', $tenantId) ->active() ->selectRaw('badge, COUNT(*) as count') ->groupBy('badge') ->pluck('count', 'badge') ->toArray(); // 전체 개수 추가 $counts['all'] = array_sum($counts); return $counts; } /** * 만료된 이슈 정리 (스케줄러에서 호출) */ public function cleanupExpiredIssues(): int { return TodayIssue::where('expires_at', '<', now())->delete(); } /** * 상대 시간 포맷팅 */ private function formatRelativeTime(?Carbon $datetime): string { if (! $datetime) { return ''; } $now = Carbon::now(); $diffInMinutes = $now->diffInMinutes($datetime); $diffInHours = $now->diffInHours($datetime); $diffInDays = $now->diffInDays($datetime); if ($diffInMinutes < 60) { return __('message.today_issue.time_minutes_ago', ['minutes' => max(1, $diffInMinutes)]); } if ($diffInHours < 24) { return __('message.today_issue.time_hours_ago', ['hours' => $diffInHours]); } if ($diffInDays == 1) { return __('message.today_issue.time_yesterday'); } if ($diffInDays < 7) { return __('message.today_issue.time_days_ago', ['days' => $diffInDays]); } return $datetime->format('Y-m-d'); } }