From 444ef805722d9fd0c28760b294703682bd7ad7fe Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 18:06:20 +0800 Subject: [PATCH 01/11] fix(overview): Show the exception to the calculation issue --- src/Controller/OverviewCardsController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Controller/OverviewCardsController.php b/src/Controller/OverviewCardsController.php index a2c374b..cb4c472 100644 --- a/src/Controller/OverviewCardsController.php +++ b/src/Controller/OverviewCardsController.php @@ -39,9 +39,10 @@ public function points( ): Response { try { $points = $pointCalculationService->calculate($user); - } catch (\Throwable) { + } catch (\Throwable $e) { $logger->warning('Failed to calculate the points for the user.', [ 'user' => $user->getId(), + 'exception' => $e, ]); $points = 0; } From 88869515f507fff0fe6492c5da25506a3d463e1f Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 18:09:07 +0800 Subject: [PATCH 02/11] feat(points): Implement weekly question XP Fixed #33 --- src/Service/PointCalculationService.php | 89 ++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/src/Service/PointCalculationService.php b/src/Service/PointCalculationService.php index 0789bda..ea3a95a 100644 --- a/src/Service/PointCalculationService.php +++ b/src/Service/PointCalculationService.php @@ -37,6 +37,10 @@ final class PointCalculationService // HintOpenEvent public static int $hintOpenEventPoint = 2; + // Weekly Question XP (#33) + public const int weeklyMinSolvedQuestion = 5; + public const int weeklyPerQuestionXP = 4; + public function __construct( private readonly SolutionEventRepository $solutionEventRepository, private readonly SolutionVideoEventRepository $solutionVideoEventRepository, @@ -54,7 +58,9 @@ public function calculate(User $user): int + $this->calculateSolutionQuestionPoints($user) + $this->calculateFirstSolutionPoints($user) + $this->calculateSolutionVideoPoints($user) - + $this->calculateHintOpenPoints($user); + + $this->calculateHintOpenPoints($user) + + $this->calculateWeeklySolvedPunishPoints($user) + ; } /** @@ -179,4 +185,85 @@ protected function calculateHintOpenPoints(User $user): int return -1 * \count($hintOpenEvents) * self::$hintOpenEventPoint; } + + /** + * Calculate the weekly solved question punish points. + * + * @return int The punish points, negative value + */ + protected function calculateWeeklySolvedPunishPoints(User $user): int + { + $weeklyMinSolvedQuestion = self::weeklyMinSolvedQuestion; + $weeklyPerQuestionXP = self::weeklyPerQuestionXP; + + // Current date and week + $currentDate = new \DateTime(); + + // Fetch the first attempt + /** + * @var array{firstAttemptDate: string|null} $firstAttempt + */ + $firstAttempt = $this->solutionEventRepository->createQueryBuilder('e') + ->select('MIN(e.createdAt) AS firstAttemptDate') + ->where('e.submitter = :submitter') + ->setParameter('submitter', $user) + ->getQuery() + ->getOneOrNullResult(); + + if (null === $firstAttempt['firstAttemptDate']) { + return 0; + } + + // Ensure startDate is set to the start of the ISO week + $startDate = (new \DateTime($firstAttempt['firstAttemptDate'])) + ->setTime(0, 0) + ->modify('Monday this week'); + + // Prepare the query to fetch counts per week + $qb = $this->solutionEventRepository->createQueryBuilder('e') + ->select('YEAR(e.createdAt) AS year', 'WEEK(e.createdAt) AS week', 'COUNT(e.id) AS cnt') + ->where('e.submitter = :submitter') + ->andWhere('e.status = :status') + ->andWhere('e.createdAt BETWEEN :start AND :end') + ->groupBy('year', 'week') + ->setParameter('submitter', $user) + ->setParameter('status', SolutionEventStatus::Passed) + ->setParameter('start', $startDate) + ->setParameter('end', $currentDate) + ; + + /** + * @var array $result + */ + $result = $qb->getQuery()->getResult(); + + // Index the result by "year-week" + $resultIndexed = []; + foreach ($result as $row) { + $key = \sprintf('%d-%02d', $row['year'], $row['week']); + $resultIndexed[$key] = $row['cnt']; + } + + // Initialize punish points + $punishPoints = 0; + + // Initialize date iteration + $iterDate = clone $startDate; + + while ($iterDate < $currentDate) { + $year = (int) $iterDate->format('o'); + $week = (int) $iterDate->format('W'); + $key = \sprintf('%d-%02d', $year, $week); + $cnt = $resultIndexed[$key] ?? 0; + + if ($cnt < $weeklyMinSolvedQuestion) { + $punishPoints += $weeklyPerQuestionXP * ($weeklyMinSolvedQuestion - $cnt); + } + + // Move to next week + $iterDate->modify('+1 week'); + } + + return -$punishPoints; + } } From 84ac9c4ac60c5f7fb3dc9373d6376c689ef2d773 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 18:16:04 +0800 Subject: [PATCH 03/11] refactor: Use const int for constant values --- src/Controller/CommentsController.php | 4 +-- src/Controller/OverviewCardsController.php | 4 +-- src/Repository/QuestionRepository.php | 6 ++-- src/Service/PointCalculationService.php | 36 +++++++++---------- .../Challenge/Instruction/Modal.php | 2 +- .../ResultPresenterModule/EventPresenter.php | 6 ++-- .../ResultPresenterModule/Pagination.php | 6 ++-- .../Challenge/ResultPresenterModule/Table.php | 4 +-- .../Challenge/SolutionVideoModal.php | 6 ++-- .../Questions/FilterableSection.php | 2 +- 10 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Controller/CommentsController.php b/src/Controller/CommentsController.php index 99e13fb..c249c9e 100644 --- a/src/Controller/CommentsController.php +++ b/src/Controller/CommentsController.php @@ -23,7 +23,7 @@ class CommentsController extends AbstractController * * You should get it from `app.scss`. */ - private static string $primaryColor = '#4154f1'; + private const string primaryColor = '#4154f1'; #[Route('/', name: '')] public function index(#[CurrentUser] User $user, CommentRepository $commentRepository): Response @@ -67,7 +67,7 @@ public function likes( 'datasets' => [ [ 'label' => $translator->trans('charts.likes_of_each_comment'), - 'backgroundColor' => self::$primaryColor, + 'backgroundColor' => self::primaryColor, 'data' => array_map(fn ($comment) => $comment['count'], $likesOfEachComment), ], ], diff --git a/src/Controller/OverviewCardsController.php b/src/Controller/OverviewCardsController.php index cb4c472..7b11896 100644 --- a/src/Controller/OverviewCardsController.php +++ b/src/Controller/OverviewCardsController.php @@ -26,7 +26,7 @@ class OverviewCardsController extends AbstractController * * You should get it from `app.scss`. */ - private static string $primaryColor = '#4154f1'; + private const string primaryColor = '#4154f1'; /** * Retrieve the card showing the experience point (XP). @@ -175,7 +175,7 @@ public function eventDailyChart( 'datasets' => [ [ 'label' => $translator->trans('charts.event_daily_chart'), - 'backgroundColor' => self::$primaryColor, + 'backgroundColor' => self::primaryColor, 'data' => array_map(fn ($event) => $event['count'], $events), ], ], diff --git a/src/Repository/QuestionRepository.php b/src/Repository/QuestionRepository.php index 491c56b..7859c68 100644 --- a/src/Repository/QuestionRepository.php +++ b/src/Repository/QuestionRepository.php @@ -16,7 +16,7 @@ */ class QuestionRepository extends ServiceEntityRepository { - public static int $pageSize = 12; + public const int pageSize = 12; public function __construct( ManagerRegistry $registry, @@ -86,8 +86,8 @@ public function search(?string $query, ?string $type, int $page, ?int $pageSize } return $this->searchService->search($this->getEntityManager(), Question::class, $query ?? '', [ - 'limit' => $pageSize ?? self::$pageSize, - 'offset' => ($page - 1) * ($pageSize ?? self::$pageSize), + 'limit' => $pageSize ?? self::pageSize, + 'offset' => ($page - 1) * ($pageSize ?? self::pageSize), 'filter' => $filters, 'sort' => ['id:asc'], ]); diff --git a/src/Service/PointCalculationService.php b/src/Service/PointCalculationService.php index ea3a95a..f276e55 100644 --- a/src/Service/PointCalculationService.php +++ b/src/Service/PointCalculationService.php @@ -19,23 +19,23 @@ final class PointCalculationService { - public static int $base = 500; + public const int base = 500; // SolutionEvent - public static int $solutionEventEasyPoint = 10; - public static int $solutionEventMediumEvent = 20; - public static int $solutionEventHardEvent = 30; + public const int solutionEventEasyPoint = 10; + public const int solutionEventMediumEvent = 20; + public const int solutionEventHardEvent = 30; // FirstSolver - public static int $firstSolverPoint = 10; + public const int firstSolverPoint = 10; // SolutionVideoEvent - public static int $solutionVideoEventEasy = 6; - public static int $solutionVideoEventMedium = 12; - public static int $solutionVideoEventHard = 18; + public const int solutionVideoEventEasy = 6; + public const int solutionVideoEventMedium = 12; + public const int solutionVideoEventHard = 18; // HintOpenEvent - public static int $hintOpenEventPoint = 2; + public const int hintOpenEventPoint = 2; // Weekly Question XP (#33) public const int weeklyMinSolvedQuestion = 5; @@ -54,7 +54,7 @@ public function __construct( */ public function calculate(User $user): int { - return self::$base + return self::base + $this->calculateSolutionQuestionPoints($user) + $this->calculateFirstSolutionPoints($user) + $this->calculateSolutionVideoPoints($user) @@ -77,9 +77,9 @@ protected function calculateSolutionQuestionPoints(User $user): int $questions = $this->solutionEventRepository->findSolvedQuestions($user); return array_reduce($questions, fn (int $carry, Question $question) => $carry + match ($question->getDifficulty()) { - QuestionDifficulty::Easy => self::$solutionEventEasyPoint, - QuestionDifficulty::Medium => self::$solutionEventMediumEvent, - QuestionDifficulty::Hard => self::$solutionEventHardEvent, + QuestionDifficulty::Easy => self::solutionEventEasyPoint, + QuestionDifficulty::Medium => self::solutionEventMediumEvent, + QuestionDifficulty::Hard => self::solutionEventHardEvent, default => 0, }, 0); } @@ -114,7 +114,7 @@ protected function calculateFirstSolutionPoints(User $user): int foreach ($questions as $question) { $firstSolver = $this->listFirstSolversOfQuestion($question, $user->getGroup()); if (null !== $firstSolver && $firstSolver === $user->getId()) { - $points += self::$firstSolverPoint; + $points += self::firstSolverPoint; } } @@ -169,9 +169,9 @@ protected function calculateSolutionVideoPoints(User $user): int foreach ($questions as $question) { $questionPointsPair[$question->getId()] = match ($question->getDifficulty()) { - QuestionDifficulty::Easy => self::$solutionVideoEventEasy, - QuestionDifficulty::Medium => self::$solutionVideoEventMedium, - QuestionDifficulty::Hard => self::$solutionVideoEventHard, + QuestionDifficulty::Easy => self::solutionVideoEventEasy, + QuestionDifficulty::Medium => self::solutionVideoEventMedium, + QuestionDifficulty::Hard => self::solutionVideoEventHard, default => 0, }; } @@ -183,7 +183,7 @@ protected function calculateHintOpenPoints(User $user): int { $hintOpenEvents = $this->hintOpenEventRepository->findByUser($user); - return -1 * \count($hintOpenEvents) * self::$hintOpenEventPoint; + return -1 * \count($hintOpenEvents) * self::hintOpenEventPoint; } /** diff --git a/src/Twig/Components/Challenge/Instruction/Modal.php b/src/Twig/Components/Challenge/Instruction/Modal.php index fbe7df7..d91f420 100644 --- a/src/Twig/Components/Challenge/Instruction/Modal.php +++ b/src/Twig/Components/Challenge/Instruction/Modal.php @@ -36,7 +36,7 @@ final class Modal public function getCost(): int { - return PointCalculationService::$hintOpenEventPoint; + return PointCalculationService::hintOpenEventPoint; } /** diff --git a/src/Twig/Components/Challenge/ResultPresenterModule/EventPresenter.php b/src/Twig/Components/Challenge/ResultPresenterModule/EventPresenter.php index 30bd38f..0f4fc53 100644 --- a/src/Twig/Components/Challenge/ResultPresenterModule/EventPresenter.php +++ b/src/Twig/Components/Challenge/ResultPresenterModule/EventPresenter.php @@ -34,7 +34,7 @@ public function __construct( */ public function getEvents(): array { - return \array_slice($this->getData(), 0, self::$limit); + return \array_slice($this->getData(), 0, self::limit); } /** @@ -50,8 +50,8 @@ protected function getData(): array return $this->solutionEventRepository->findUserQuestionEvents( question: $this->question, user: $this->user, - limit: self::$limit + 1 /* more? */, - offset: ($this->page - 1) * self::$limit, + limit: self::limit + 1 /* more? */, + offset: ($this->page - 1) * self::limit, ); } } diff --git a/src/Twig/Components/Challenge/ResultPresenterModule/Pagination.php b/src/Twig/Components/Challenge/ResultPresenterModule/Pagination.php index 90a50d8..358dcb7 100644 --- a/src/Twig/Components/Challenge/ResultPresenterModule/Pagination.php +++ b/src/Twig/Components/Challenge/ResultPresenterModule/Pagination.php @@ -23,7 +23,7 @@ */ trait Pagination { - private static int $limit = 7; + private const int limit = 7; #[LiveProp] public int $page = 1; @@ -40,7 +40,7 @@ public function hasPrevious(): bool public function hasNext(): bool { - return \count($this->getData()) > self::$limit; + return \count($this->getData()) > self::limit; } #[LiveAction] @@ -57,6 +57,6 @@ public function goNext(): void public function getCurrentOffset(): int { - return ($this->page - 1) * self::$limit; + return ($this->page - 1) * self::limit; } } diff --git a/src/Twig/Components/Challenge/ResultPresenterModule/Table.php b/src/Twig/Components/Challenge/ResultPresenterModule/Table.php index 6e83631..ef70ab1 100644 --- a/src/Twig/Components/Challenge/ResultPresenterModule/Table.php +++ b/src/Twig/Components/Challenge/ResultPresenterModule/Table.php @@ -42,7 +42,7 @@ public function getHeader(): array */ protected function getData(): array { - return \array_slice($this->result, ($this->page - 1) * self::$limit, self::$limit + 1); + return \array_slice($this->result, ($this->page - 1) * self::limit, self::limit + 1); } /** @@ -52,6 +52,6 @@ protected function getData(): array */ public function getRows(): array { - return \array_slice($this->getData(), 0, self::$limit); + return \array_slice($this->getData(), 0, self::limit); } } diff --git a/src/Twig/Components/Challenge/SolutionVideoModal.php b/src/Twig/Components/Challenge/SolutionVideoModal.php index 0a6516a..51acf24 100644 --- a/src/Twig/Components/Challenge/SolutionVideoModal.php +++ b/src/Twig/Components/Challenge/SolutionVideoModal.php @@ -34,9 +34,9 @@ public function __construct( public function getCost(): int { return match ($this->question->getDifficulty()) { - QuestionDifficulty::Easy => PointCalculationService::$solutionVideoEventEasy, - QuestionDifficulty::Medium => PointCalculationService::$solutionVideoEventMedium, - QuestionDifficulty::Hard => PointCalculationService::$solutionVideoEventHard, + QuestionDifficulty::Easy => PointCalculationService::solutionVideoEventEasy, + QuestionDifficulty::Medium => PointCalculationService::solutionVideoEventMedium, + QuestionDifficulty::Hard => PointCalculationService::solutionVideoEventHard, default => 0, }; } diff --git a/src/Twig/Components/Questions/FilterableSection.php b/src/Twig/Components/Questions/FilterableSection.php index 810b9f2..6c6e7ee 100644 --- a/src/Twig/Components/Questions/FilterableSection.php +++ b/src/Twig/Components/Questions/FilterableSection.php @@ -92,7 +92,7 @@ public function hasNext(): bool type: $this->type, ); - $totalPage = ceil($total / QuestionRepository::$pageSize); + $totalPage = ceil($total / QuestionRepository::pageSize); return $this->getCurrentPage() < $totalPage; } From bc7213fecbed486176e5e838c17f17648defb10e Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:21:18 +0800 Subject: [PATCH 04/11] feat(feedback/admin): Allow filtering status --- .../Admin/FeedbackCrudController.php | 6 +- src/Controller/Admin/FeedbackStatusFilter.php | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/Controller/Admin/FeedbackStatusFilter.php diff --git a/src/Controller/Admin/FeedbackCrudController.php b/src/Controller/Admin/FeedbackCrudController.php index 8733ba1..3a82e37 100644 --- a/src/Controller/Admin/FeedbackCrudController.php +++ b/src/Controller/Admin/FeedbackCrudController.php @@ -55,7 +55,11 @@ public function configureFields(string $pageName): iterable public function configureFilters(Filters $filters): Filters { - return $filters->add('sender')->add('type')->add('status'); + return $filters + ->add('sender') + ->add('type') + ->add(FeedbackStatusFilter::new('status')) + ; } public function configureActions(Actions $actions): Actions diff --git a/src/Controller/Admin/FeedbackStatusFilter.php b/src/Controller/Admin/FeedbackStatusFilter.php new file mode 100644 index 0000000..d55c2d2 --- /dev/null +++ b/src/Controller/Admin/FeedbackStatusFilter.php @@ -0,0 +1,58 @@ +getValue()) { + self::filterBacklog => [FeedbackStatus::Backlog], + self::filterNewInProgress => [FeedbackStatus::New, FeedbackStatus::InProgress], + self::filterResolvedClosed => [FeedbackStatus::Resolved, FeedbackStatus::Closed], + default => [], + }; + + $queryBuilder + ->andWhere("{$filterDataDto->getEntityAlias()}.{$filterDataDto->getProperty()} IN (:status)") + ->setParameter( + 'status', + $statusSet, + ) + ; + } + + public static function new(string $propertyName, ?string $label = null): self + { + return (new self()) + ->setFilterFqcn(self::class) + ->setProperty($propertyName) + ->setLabel($label) + ->setFormType(ChoiceType::class) + ->setFormTypeOptions([ + 'choices' => [ + 'Backlog' => self::filterBacklog, + 'New & In Progress' => self::filterNewInProgress, + 'Resolved & Closed' => self::filterResolvedClosed, + ], + ]) + ; + } +} From 5bce56127f9f0966f763b199ef32d6338029a055 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:21:54 +0800 Subject: [PATCH 05/11] l10n(feedback/admin): Filter item --- translations/messages.zh_TW.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/translations/messages.zh_TW.yaml b/translations/messages.zh_TW.yaml index 9b1fe31..b4867d6 100644 --- a/translations/messages.zh_TW.yaml +++ b/translations/messages.zh_TW.yaml @@ -52,6 +52,8 @@ Contact: 聯絡方式 Metadata: 中繼資料 Mark Resolved: 標記為已解決 Mark Closed: 標記為已關閉 +New & In Progress: 新問題 & 處理中 +Resolved & Closed: 已解決 & 已關閉 challenge.error-type.user: 輸入錯誤 challenge.error-type.server: 伺服器錯誤 From 95df0f0c884e9a389afbccc846d4b72b99ef784d Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:29:31 +0800 Subject: [PATCH 06/11] feat(feedback): Allow commenting on Feedback --- migrations/Version20241006112324.php | 32 ++++++++++++++++++++++++++++ src/Entity/Feedback.php | 15 +++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 migrations/Version20241006112324.php diff --git a/migrations/Version20241006112324.php b/migrations/Version20241006112324.php new file mode 100644 index 0000000..d94e8f2 --- /dev/null +++ b/migrations/Version20241006112324.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE feedback ADD comment TEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE feedback DROP comment'); + } +} diff --git a/src/Entity/Feedback.php b/src/Entity/Feedback.php index d347980..22a9901 100644 --- a/src/Entity/Feedback.php +++ b/src/Entity/Feedback.php @@ -58,6 +58,9 @@ class Feedback #[ORM\Column] private \DateTimeImmutable $updated_at; + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $comment = null; + public function getId(): ?Ulid { return $this->id; @@ -191,4 +194,16 @@ public function updateUpdatedAtValue(): void { $this->updated_at = new \DateTimeImmutable(); } + + public function getComment(): ?string + { + return $this->comment; + } + + public function setComment(?string $comment): static + { + $this->comment = $comment; + + return $this; + } } From 2415c1a2d00fe45dbb089df45042454639ab5c87 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:29:53 +0800 Subject: [PATCH 07/11] feat(feedback/admin): Allow commenting on feedback --- src/Controller/Admin/FeedbackCrudController.php | 14 ++++++++------ translations/messages.zh_TW.yaml | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Controller/Admin/FeedbackCrudController.php b/src/Controller/Admin/FeedbackCrudController.php index 3a82e37..e052365 100644 --- a/src/Controller/Admin/FeedbackCrudController.php +++ b/src/Controller/Admin/FeedbackCrudController.php @@ -18,6 +18,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; +use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; @@ -41,15 +42,16 @@ public function configureFields(string $pageName): iterable { return [ IdField::new('id')->setDisabled(), - AssociationField::new('sender')->hideWhenUpdating(), - TextField::new('title')->hideWhenUpdating(), - TextEditorField::new('description')->hideWhenUpdating(), ChoiceField::new('type')->hideWhenUpdating(), - TextField::new('contact')->hideWhenUpdating(), - ArrayField::new('metadata')->hideWhenUpdating(), + AssociationField::new('sender')->hideOnIndex()->hideWhenUpdating(), + TextField::new('title')->hideWhenUpdating(), + TextEditorField::new('description')->hideOnIndex()->hideWhenUpdating(), + TextField::new('contact')->hideOnIndex()->hideWhenUpdating(), + ArrayField::new('metadata')->hideOnIndex()->hideWhenUpdating(), + TextareaField::new('comment', 'feedback.comment')->hideOnIndex(), ChoiceField::new('status'), DateTimeField::new('createdAt', 'Created at')->setDisabled(), - DateTimeField::new('updatedAt', 'Updated at')->setDisabled(), + DateTimeField::new('updatedAt', 'Updated at')->hideOnIndex()->setDisabled(), ]; } diff --git a/translations/messages.zh_TW.yaml b/translations/messages.zh_TW.yaml index b4867d6..bcf36b3 100644 --- a/translations/messages.zh_TW.yaml +++ b/translations/messages.zh_TW.yaml @@ -136,6 +136,7 @@ feedback: closed: 已關閉 marked: 已經將選擇的回饋標記為「%status%」。 + comment: 回饋備註 notification: on-feedback-created: From 4e17850fd6c212ed669495fdf33596c0fbbbebed Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:34:31 +0800 Subject: [PATCH 08/11] refactor(feedback/admin): Improve index table --- src/Controller/Admin/FeedbackCrudController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Controller/Admin/FeedbackCrudController.php b/src/Controller/Admin/FeedbackCrudController.php index e052365..85b9f84 100644 --- a/src/Controller/Admin/FeedbackCrudController.php +++ b/src/Controller/Admin/FeedbackCrudController.php @@ -41,9 +41,9 @@ public static function getEntityFqcn(): string public function configureFields(string $pageName): iterable { return [ - IdField::new('id')->setDisabled(), + IdField::new('id')->hideOnIndex()->setDisabled(), ChoiceField::new('type')->hideWhenUpdating(), - AssociationField::new('sender')->hideOnIndex()->hideWhenUpdating(), + AssociationField::new('sender')->hideWhenUpdating(), TextField::new('title')->hideWhenUpdating(), TextEditorField::new('description')->hideOnIndex()->hideWhenUpdating(), TextField::new('contact')->hideOnIndex()->hideWhenUpdating(), From 69a8a2512e61874529b4f6df4a187ea087d0420c Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:44:56 +0800 Subject: [PATCH 09/11] docs(points): Update point rules docs --- src/Service/PointCalculationService.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Service/PointCalculationService.php b/src/Service/PointCalculationService.php index f276e55..5710de2 100644 --- a/src/Service/PointCalculationService.php +++ b/src/Service/PointCalculationService.php @@ -66,7 +66,8 @@ public function calculate(User $user): int /** * Calculate the total points of the solution events. * - * 每位同學基本經驗值500點,成功解一題獲得經驗值增加。易:10點、中:20點、難:30點 + * Successfully solving a problem increases experience points. + * Easy: 10 points, Medium: 20 points, Hard: 30 points. * * @param User $user The user to calculate the points for * @@ -87,7 +88,7 @@ protected function calculateSolutionQuestionPoints(User $user): int /** * Calculate the points if the user is the first solver of a question. * - * 第一位解題成功者加10點。 + * The first person to solve the problem gets 10 points. * * @throws InvalidArgumentException */ @@ -150,6 +151,12 @@ function (ItemInterface $item) use ($group, $question) { ); } + /** + * Calculate the points of the solution video events. + * + * Each student will lose experience points for watching a solution video. + * Easy: 6 points, Medium: 12 points, Hard: 18 points. + */ protected function calculateSolutionVideoPoints(User $user): int { /** @@ -189,6 +196,11 @@ protected function calculateHintOpenPoints(User $user): int /** * Calculate the weekly solved question punish points. * + * You need to solve at least 5 problems each week. + * For each problem you fall short, you will lose 20 experience points. + * + * @param User $user The user to calculate the points for + * * @return int The punish points, negative value */ protected function calculateWeeklySolvedPunishPoints(User $user): int From de1549b074bebb748c489479e38213376954e1fe Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:49:46 +0800 Subject: [PATCH 10/11] fix(points): Correct property naming --- src/Service/PointCalculationService.php | 28 +++++++++---------- .../Challenge/SolutionVideoModal.php | 6 ++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Service/PointCalculationService.php b/src/Service/PointCalculationService.php index 5710de2..9b94731 100644 --- a/src/Service/PointCalculationService.php +++ b/src/Service/PointCalculationService.php @@ -23,23 +23,23 @@ final class PointCalculationService // SolutionEvent public const int solutionEventEasyPoint = 10; - public const int solutionEventMediumEvent = 20; - public const int solutionEventHardEvent = 30; + public const int solutionEventMediumPoint = 20; + public const int solutionEventHardPoint = 30; // FirstSolver public const int firstSolverPoint = 10; // SolutionVideoEvent - public const int solutionVideoEventEasy = 6; - public const int solutionVideoEventMedium = 12; - public const int solutionVideoEventHard = 18; + public const int solutionVideoEventEasyPoint = 6; + public const int solutionVideoEventMediumPoint = 12; + public const int solutionVideoEventHardPoint = 18; // HintOpenEvent public const int hintOpenEventPoint = 2; // Weekly Question XP (#33) - public const int weeklyMinSolvedQuestion = 5; - public const int weeklyPerQuestionXP = 4; + public const int weeklyMinSolvedQuestionPoint = 5; + public const int weeklyPerQuestionXpPoint = 4; public function __construct( private readonly SolutionEventRepository $solutionEventRepository, @@ -79,8 +79,8 @@ protected function calculateSolutionQuestionPoints(User $user): int return array_reduce($questions, fn (int $carry, Question $question) => $carry + match ($question->getDifficulty()) { QuestionDifficulty::Easy => self::solutionEventEasyPoint, - QuestionDifficulty::Medium => self::solutionEventMediumEvent, - QuestionDifficulty::Hard => self::solutionEventHardEvent, + QuestionDifficulty::Medium => self::solutionEventMediumPoint, + QuestionDifficulty::Hard => self::solutionEventHardPoint, default => 0, }, 0); } @@ -176,9 +176,9 @@ protected function calculateSolutionVideoPoints(User $user): int foreach ($questions as $question) { $questionPointsPair[$question->getId()] = match ($question->getDifficulty()) { - QuestionDifficulty::Easy => self::solutionVideoEventEasy, - QuestionDifficulty::Medium => self::solutionVideoEventMedium, - QuestionDifficulty::Hard => self::solutionVideoEventHard, + QuestionDifficulty::Easy => self::solutionVideoEventEasyPoint, + QuestionDifficulty::Medium => self::solutionVideoEventMediumPoint, + QuestionDifficulty::Hard => self::solutionVideoEventHardPoint, default => 0, }; } @@ -205,8 +205,8 @@ protected function calculateHintOpenPoints(User $user): int */ protected function calculateWeeklySolvedPunishPoints(User $user): int { - $weeklyMinSolvedQuestion = self::weeklyMinSolvedQuestion; - $weeklyPerQuestionXP = self::weeklyPerQuestionXP; + $weeklyMinSolvedQuestion = self::weeklyMinSolvedQuestionPoint; + $weeklyPerQuestionXP = self::weeklyPerQuestionXpPoint; // Current date and week $currentDate = new \DateTime(); diff --git a/src/Twig/Components/Challenge/SolutionVideoModal.php b/src/Twig/Components/Challenge/SolutionVideoModal.php index 51acf24..e7f0937 100644 --- a/src/Twig/Components/Challenge/SolutionVideoModal.php +++ b/src/Twig/Components/Challenge/SolutionVideoModal.php @@ -34,9 +34,9 @@ public function __construct( public function getCost(): int { return match ($this->question->getDifficulty()) { - QuestionDifficulty::Easy => PointCalculationService::solutionVideoEventEasy, - QuestionDifficulty::Medium => PointCalculationService::solutionVideoEventMedium, - QuestionDifficulty::Hard => PointCalculationService::solutionVideoEventHard, + QuestionDifficulty::Easy => PointCalculationService::solutionVideoEventEasyPoint, + QuestionDifficulty::Medium => PointCalculationService::solutionVideoEventMediumPoint, + QuestionDifficulty::Hard => PointCalculationService::solutionVideoEventHardPoint, default => 0, }; } From 07230f85c41c610194bba8c3965404788091c303 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 19:59:40 +0800 Subject: [PATCH 11/11] feat(complementary): Add Point Rules in plain Chinese --- templates/complementary/index.html.twig | 52 +++++++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/templates/complementary/index.html.twig b/templates/complementary/index.html.twig index 60d0b95..b4ff188 100644 --- a/templates/complementary/index.html.twig +++ b/templates/complementary/index.html.twig @@ -5,15 +5,11 @@ {% block title %}補充資料{% endblock %} {% block app %} -
-
-

補充資料

-
- -
-

Schema SQLs

+
+
+

Schema SQLs

-
+
{% for schema in schemas %}
@@ -41,5 +37,45 @@ {% endfor %}
+ +
+

計分規則

+ +
    +
  • 每個人有 {{ constant('\\App\\Service\\PointCalculationService::base') }} 基本分。
  • +
  • + 解一題根據難度會加分,注意重複解題分數不會累計。 +
      +
    • 簡單題:{{ constant('\\App\\Service\\PointCalculationService::solutionEventEasyPoint') }} 分
    • +
    • 中等題:{{ constant('\\App\\Service\\PointCalculationService::solutionEventMediumPoint') }} 分
    • +
    • 困難題:{{ constant('\\App\\Service\\PointCalculationService::solutionEventHardPoint') }} 分
    • +
    +
  • +
  • + 如果是第一個解出題目的人,會再加 {{ constant('\\App\\Service\\PointCalculationService::firstSolverPoint') }} 分。 +
  • +
  • + 打開解答影片根據題型會扣分。注意同題目的解答影片不會重複扣分。 +
      +
    • 簡單題:{{ constant('\\App\\Service\\PointCalculationService::solutionVideoEventEasyPoint') }} 分
    • +
    • 中等題:{{ constant('\\App\\Service\\PointCalculationService::solutionVideoEventMediumPoint') }} 分
    • +
    • 困難題:{{ constant('\\App\\Service\\PointCalculationService::solutionVideoEventHardPoint') }} 分
    • +
    +
  • +
  • + 打開提示會扣 {{ constant('\\App\\Service\\PointCalculationService::hintOpenEventPoint') }} 分。重複打開提示會重複扣分。 +
  • +
  • + 如果當周做題未達 {{ constant('\\App\\Service\\PointCalculationService::weeklyMinSolvedQuestionPoint') }} + 題,少的每題扣 {{ constant('\\App\\Service\\PointCalculationService::weeklyPerQuestionXpPoint') }} 分。 +
  • +
+ +

+ 相關計分原始碼可以參考 + GitHub 的對應程式碼

+
{% endblock %}