Skip to content
This repository was archived by the owner on Oct 15, 2025. It is now read-only.

Commit 25e7d80

Browse files
committed
refactor(admin): Move statistics page to admin page
1 parent 060aa7d commit 25e7d80

File tree

7 files changed

+155
-145
lines changed

7 files changed

+155
-145
lines changed

src/Command/CompletedQuestionStatCommand.php

Lines changed: 0 additions & 65 deletions
This file was deleted.

src/Command/LastLoginStatCommand.php

Lines changed: 0 additions & 79 deletions
This file was deleted.

src/Controller/Admin/DashboardController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public function configureMenuItems(): iterable
5050
{
5151
yield MenuItem::linkToRoute('Back to App', 'fa fa-arrow-left', 'app_home');
5252

53+
yield MenuItem::section('Statistics');
54+
yield MenuItem::linkToRoute('Last login at', 'fa fa-sign-in-alt', 'admin_statistic_last_login_at');
55+
yield MenuItem::linkToRoute('Completed Questions', 'fa fa-trophy', 'admin_statistic_completed_questions');
56+
5357
yield MenuItem::section('User management');
5458
yield MenuItem::linkToCrud('User', 'fa fa-user', User::class);
5559
yield MenuItem::linkToCrud('Group', 'fa fa-users', Group::class);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Controller\Admin;
6+
7+
use App\Entity\SolutionEventStatus;
8+
use App\Repository\QuestionRepository;
9+
use App\Repository\UserRepository;
10+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
11+
use Symfony\Component\HttpFoundation\Response;
12+
use Symfony\Component\Routing\Attribute\Route;
13+
14+
class StatisticController extends AbstractController
15+
{
16+
#[Route('/admin/statistic/last-login-at', name: 'admin_statistic_last_login_at')]
17+
public function lastLoginAt(UserRepository $userRepository): Response
18+
{
19+
/**
20+
* @var list<array{id: int, email: string, last_login_at: string|null}> $results
21+
*/
22+
$results = $userRepository->createQueryBuilder('user')
23+
->leftJoin('user.loginEvents', 'loginEvent')
24+
->select(
25+
'user.id',
26+
'user.email',
27+
'MAX(loginEvent.createdAt) AS last_login_at',
28+
)
29+
->groupBy('user.id', 'user.email')
30+
->orderBy('last_login_at', 'DESC')
31+
->getQuery()
32+
->getResult();
33+
34+
/**
35+
* @var list<array{id: int, email: string, last_login_at: string, recency: string}> $resultsWithRecency
36+
*/
37+
$resultsWithRecency = [];
38+
/**
39+
* @var list<array{id: int, email: string}> $resultsThatNeverLogin
40+
*/
41+
$resultsThatNeverLogin = [];
42+
43+
foreach ($results as $result) {
44+
$lastLoginAt = ($lastLoginAt = $result['last_login_at']) !== null
45+
? new \DateTime($lastLoginAt)
46+
: null;
47+
48+
if (null !== $lastLoginAt) {
49+
$lastLoginAtString = $lastLoginAt->format('Y-m-d H:i:s');
50+
$recency = $lastLoginAt->diff(new \DateTime())->format('%a 天');
51+
52+
$resultsWithRecency[] = [
53+
'id' => $result['id'],
54+
'email' => $result['email'],
55+
'last_login_at' => $lastLoginAtString,
56+
'recency' => $recency,
57+
];
58+
} else {
59+
$resultsThatNeverLogin[] = [
60+
'id' => $result['id'],
61+
'email' => $result['email'],
62+
];
63+
}
64+
}
65+
66+
return $this->render('admin/statistics/last_login_at.html.twig', [
67+
'results' => $resultsWithRecency + $resultsThatNeverLogin,
68+
]);
69+
}
70+
71+
#[Route('/admin/statistic/completed-questions', name: 'admin_statistic_completed_questions')]
72+
public function completedQuestions(UserRepository $userRepository, QuestionRepository $questionRepository): Response
73+
{
74+
$totalQuestions = $questionRepository->count();
75+
76+
/**
77+
* @var list<array{id: int, email: string, solved_questions: int<0, max>}> $userSolvedQuestionsCount
78+
*/
79+
$userSolvedQuestionsCount = $userRepository->createQueryBuilder('u')
80+
->select('u.id', 'u.email', 'COUNT(DISTINCT q) as solved_questions')
81+
->leftJoin('u.solutionEvents', 'se')
82+
->leftJoin('se.question', 'q')
83+
->where('se.status = :status or se is NULL')
84+
->groupBy('u.id', 'u.email')
85+
->orderBy('solved_questions', 'DESC')
86+
->setParameter('status', SolutionEventStatus::Passed)
87+
->getQuery()
88+
->getResult();
89+
90+
return $this->render('admin/statistics/completed_questions.html.twig', [
91+
'totalQuestions' => $totalQuestions,
92+
'userSolvedQuestionsCount' => $userSolvedQuestionsCount,
93+
]);
94+
}
95+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{% extends '@EasyAdmin/page/content.html.twig' %}
2+
3+
{% block content_title %}統計資料 – 完成題數{% endblock %}
4+
5+
{% block main %}
6+
<table class="table datagrid">
7+
<thead>
8+
<tr>
9+
<th><a href="#">帳號</a></th>
10+
<th><a href="#">完成題數</a></th>
11+
<th><a href="#">進度</a></th>
12+
</tr>
13+
</thead>
14+
<tbody>
15+
{% for user in userSolvedQuestionsCount %}
16+
<tr>
17+
<td><a href="{{ ea_url()
18+
.setController('App\\Controller\\Admin\\UserCrudController')
19+
.setAction('detail')
20+
.setEntityId(user.id) }}">{{ user.email }}</a></td>
21+
<td>{{ user.solved_questions }}</td>
22+
<td>{{ totalQuestions > 0 ? (user.solved_questions / totalQuestions * 100)|round(2) : 0 }}%</td>
23+
</tr>
24+
{% endfor %}
25+
</tbody>
26+
</table>
27+
{% endblock %}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{% extends '@EasyAdmin/page/content.html.twig' %}
2+
3+
{% block content_title %}統計資料 – 上次登入時間{% endblock %}
4+
5+
{% block main %}
6+
<table class="table datagrid">
7+
<thead>
8+
<tr>
9+
<th><a href="#">帳號</a></th>
10+
<th><a href="#">上次登入時間</a></th>
11+
<th><a href="#">距今天數</a></th>
12+
</tr>
13+
</thead>
14+
<tbody>
15+
{% for result in results %}
16+
<tr>
17+
<td><a href="{{ ea_url()
18+
.setController('App\\Controller\\Admin\\UserCrudController')
19+
.setAction('detail')
20+
.setEntityId(result.id) }}">{{ result.email }}</a></td>
21+
<td>{{ result.last_login_at ?? '沒登入過' }}</td>
22+
<td>{{ result.recency ?? 'N/A' }}</td>
23+
</tr>
24+
{% endfor %}
25+
</tbody>
26+
</table>
27+
{% endblock %}

translations/messages.zh_TW.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
User: 使用者
2-
Dashboard: 儀表板
32
User management: 使用者管理
43
Question management: 題庫管理
54
Schema: 結構
@@ -70,6 +69,8 @@ Text Content: 文字內容
7069
HTML Content: HTML 內容
7170
EmailTemplates: 郵件範本
7271
Last login at: 最後登入時間
72+
Statistics: 統計資料
73+
Completed Questions: 完成題數
7374

7475
result_presenter.tabs.result: 執行結果
7576
result_presenter.tabs.answer: 正確答案

0 commit comments

Comments
 (0)