From 225b96c644c13160daf9372d40e025d8a5de8718 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Mon, 26 Jan 2026 15:22:31 +0600 Subject: [PATCH 1/8] Added New Quiz Attempts Courses Tab Page Design for student --- .../frontend/dashboard/_quiz-attempts.scss | 7 ++ classes/Utils.php | 2 +- models/QuizModel.php | 5 +- .../dashboard/components/quiz-attempt-row.php | 83 ++++++++++---- .../components/quiz-attempts-group.php | 31 ++++++ templates/dashboard/my-quiz-attempts.php | 104 +++++++++++++----- templates/dashboard/quiz-attempts.php | 12 +- 7 files changed, 190 insertions(+), 54 deletions(-) diff --git a/assets/src/scss/frontend/dashboard/_quiz-attempts.scss b/assets/src/scss/frontend/dashboard/_quiz-attempts.scss index 8e3c680c1a..cecac75da1 100644 --- a/assets/src/scss/frontend/dashboard/_quiz-attempts.scss +++ b/assets/src/scss/frontend/dashboard/_quiz-attempts.scss @@ -197,6 +197,13 @@ justify-content: space-between; } } + + .tutor-student-attempt-detail { + text-decoration: none; + @include tutor-breakpoint-up(sm) { + display: none; + } + } } .tutor-quiz-item-marks { diff --git a/classes/Utils.php b/classes/Utils.php index bbc41e75bd..78c41c55d3 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -8548,7 +8548,7 @@ public function is_tutor_dashboard( $subpage = null ) { * * @return boolean */ - public function is_tutor_frontend_dashboard( $subpage = null ) { + public function is_tutor_frontend_dashboard( $subpage = '' ) { global $wp_query; if ( $wp_query->is_page ) { $dashboard_page = $this->array_get( 'tutor_dashboard_page', $wp_query->query_vars ); diff --git a/models/QuizModel.php b/models/QuizModel.php index b8c13cdb40..2042d70ab7 100644 --- a/models/QuizModel.php +++ b/models/QuizModel.php @@ -126,6 +126,7 @@ public static function format_quiz_attempts( array $quiz_attempts, string $filte 'time_taken' => $attempt_time ?? '', 'date' => $start_time ?? '', 'student' => $quiz_attempt->display_name ?? '', + 'attempt_info' => maybe_unserialize( $quiz_attempt->attempt_info ) ?? array(), ); if ( ! isset( $formatted_attempts[ $quiz_id ]['attempts'] ) ) { @@ -606,8 +607,8 @@ function ( $id ) { $date_filter = '' != $date_filter ? " AND DATE(quiz_attempts.attempt_started_at) = '$date_filter' " : ''; $user_filter = $user_id ? ' AND user_id=\'' . esc_sql( $user_id ) . '\' ' : ''; - $limit_offset = $count_only ? '' : " LIMIT {$start}, {$limit} "; - $select_col = $count_only ? ' COUNT(DISTINCT quiz_attempts.attempt_id) ' : ' quiz_attempts.*, users.*, quiz.* '; + $limit_offset = $count_only || ( 0 === $limit && 0 === $start ) ? '' : " LIMIT {$start}, {$limit} "; + $select_col = $count_only ? ' COUNT(DISTINCT quiz_attempts.attempt_id) ' : ' quiz_attempts.*, quiz.* '; $attempt_type = $all_attempt ? '' : " AND quiz_attempts.attempt_status != 'attempt_started' "; diff --git a/templates/dashboard/components/quiz-attempt-row.php b/templates/dashboard/components/quiz-attempt-row.php index 6a39432407..877aa7e6db 100644 --- a/templates/dashboard/components/quiz-attempt-row.php +++ b/templates/dashboard/components/quiz-attempt-row.php @@ -12,7 +12,6 @@ use Tutor\Components\Badge; use Tutor\Components\Button; use Tutor\Components\Constants\Size; -use Tutor\Components\Constants\Variant; use Tutor\Components\Popover; use Tutor\Components\PreviewTrigger; use TUTOR\Icon; @@ -53,13 +52,40 @@ ->attr( '@click', $delete_attr ) ->variant( 'secondary' ); +$details_item = array( + 'tag' => 'a', + 'content' => __( 'Details', 'tutor' ), + 'icon' => tutor_utils()->get_svg_icon( Icon::RESOURCES ), + 'attr' => array( 'href' => $review_url ), +); + +$delete_item = array( + 'tag' => 'a', + 'content' => __( 'Delete', 'tutor' ), + 'icon' => tutor_utils()->get_svg_icon( Icon::DELETE_2 ), + 'attr' => array( + 'href' => '#', + '@click' => $click_attr, + ), +); + +$retry_item = array( + 'tag' => 'a', + 'content' => __( 'Retry', 'tutor' ), + 'icon' => tutor_utils()->get_svg_icon( Icon::RELOAD ), + 'attr' => array( 'href' => get_post_permalink( $quiz_id ) ), +); ?>
- +
-
+
+ +
1 ) : ?> + +
+ +
+
+ +
+ +
+
+
@@ -142,35 +182,36 @@ $badge->render(); } - - Popover::make() + if ( $is_student && ! $should_retry ) { + Popover::make() + ->trigger( $kebab_button ) + ->placement( 'bottom' ) + ->menu_item( + $details_item + ) + ->render(); + } else { + Popover::make() ->trigger( $kebab_button ) ->placement( 'bottom' ) ->menu_item( - array( - 'tag' => 'a', - 'content' => __( 'Details', 'tutor' ), - 'icon' => tutor_utils()->get_svg_icon( Icon::RESOURCES ), - 'attr' => array( 'href' => $review_url ), - ) + $is_student ? $retry_item : $details_item ) ->menu_item( - array( - 'tag' => 'a', - 'content' => __( 'Delete', 'tutor' ), - 'icon' => tutor_utils()->get_svg_icon( Icon::DELETE_2 ), - 'attr' => array( - 'href' => '#', - '@click' => $click_attr, - ), - ) + $is_student ? $details_item : $delete_item ) - ->render() + ->render(); + } + + ?>
+ +
render(); ?> render(); ?>
+ diff --git a/templates/dashboard/components/quiz-attempts-group.php b/templates/dashboard/components/quiz-attempts-group.php index fa858fa9d4..d162e8855f 100644 --- a/templates/dashboard/components/quiz-attempts-group.php +++ b/templates/dashboard/components/quiz-attempts-group.php @@ -9,7 +9,13 @@ * @since 4.0.0 */ +use Tutor\Components\Button; +use Tutor\Components\Constants\Size; +use TUTOR\Icon; +use TUTOR\User; + $attempts_count = tutor_utils()->count( $attempts ); +$is_student = User::is_student( get_current_user_id() ) && tutor_utils()->is_enrolled( $course_id, get_current_user_id(), false ); if ( empty( $attempts ) ) { return; @@ -18,7 +24,22 @@ $first_attempt = $attempts[0]; $remaining_attempts = array_slice( $attempts, 1 ); +$retry_button = Button::make()->label( __( 'Retry', 'tutor' ) ) + ->icon( Icon::RELOAD ) + ->size( Size::MEDIUM ) + ->tag( 'a' ) + ->variant( 'primary' ) + ->attr( 'href', get_post_permalink( $quiz_id ) ); + +$attempt_info = $first_attempt['attempt_info'] ?? array(); + +$should_retry = false; +if ( tutor_utils()->count( $attempt_info ) ) { + $allowed_attempts = (int) $attempt_info['attempts_allowed'] ?? 0; + $feedback_mode = $attempt_info['feedback_mode'] ?? ''; + $should_retry = 'retry' === $feedback_mode && $attempts_count < $allowed_attempts; +} ?>
@@ -35,6 +56,8 @@ 'quiz_id' => $quiz_id, 'attempts_count' => $attempts_count, 'attempt_id' => $first_attempt['attempt_id'] ?? 0, + 'is_student' => $is_student, + 'should_retry' => $should_retry, ) ); ?> @@ -55,10 +78,18 @@ 'quiz_id' => $quiz_id, 'attempt_id' => $attempt['attempt_id'] ?? 0, 'course_id' => $course_id, + 'is_student' => $is_student, + 'should_retry' => $should_retry, ) ); ?>
+ + +
+ render(); ?> +
+
diff --git a/templates/dashboard/my-quiz-attempts.php b/templates/dashboard/my-quiz-attempts.php index 520ea3c4e3..0fa898b718 100644 --- a/templates/dashboard/my-quiz-attempts.php +++ b/templates/dashboard/my-quiz-attempts.php @@ -9,8 +9,14 @@ * @since 1.1.2 */ +use Tutor\Components\DropdownFilter; +use Tutor\Components\EmptyState; +use Tutor\Components\Pagination; +use Tutor\Components\Sorting; +use TUTOR\Icon; use TUTOR\Input; use Tutor\Models\QuizModel; +use TUTOR\Quiz_Attempts_List; if ( Input::has( 'view_quiz_attempt_id' ) ) { // Load single attempt details if ID provided. @@ -18,36 +24,84 @@ return; } -$item_per_page = tutor_utils()->get_option( 'pagination_per_page' ); -$current_page = max( 1, Input::get( 'current_page', 1, Input::TYPE_INT ) ); -$offset = ( $current_page - 1 ) * $item_per_page; +$item_per_page = tutor_utils()->get_option( 'pagination_per_page' ); +$current_page = max( 1, Input::get( 'current_page', 1, Input::TYPE_INT ) ); +$offset = ( $current_page - 1 ) * $item_per_page; +$quiz_attempt_obj = new Quiz_Attempts_List( false ); + // Filter params. -$course_filter = Input::get( 'course-id' ); $order_filter = Input::get( 'order', 'DESC' ); -$date_filter = Input::get( 'date', '' ); $course_id = isset( $course_id ) ? $course_id : array(); +$result_filter = Input::get( 'result', '' ); -$quiz_attempts = QuizModel::get_quiz_attempts_by_course_ids( $offset, $item_per_page, $course_id, '', $course_filter, $date_filter, $order_filter, get_current_user_id() ); +$quiz_attempts = QuizModel::get_quiz_attempts_by_course_ids( 0, 0, $course_id, '', '', '', $order_filter, get_current_user_id() ); +$quiz_attempts_formatted = QuizModel::format_quiz_attempts( $quiz_attempts, $result_filter ); +$quiz_attempts_list = array_slice( $quiz_attempts_formatted, $offset, $item_per_page, true ); +$quiz_attempts_count = (int) count( $quiz_attempts_formatted ); -?> +$nav_links = $quiz_attempt_obj->get_quiz_attempts_nav_data( $quiz_attempts, $quiz_attempts_count, get_pagenum_link(), $result_filter ); -
-path . '/views/quiz/attempt-table.php', - array( - 'attempt_list' => $quiz_attempts, - 'context' => 'frontend-dashboard-my-attempts', - ) -); -$pagination_data = array( - 'total_items' => $quiz_attempts_count, - 'per_page' => $item_per_page, - 'paged' => $current_page, -); -$pagination_template_frontend = tutor()->path . 'templates/dashboard/elements/pagination.php'; -tutor_load_template_from_custom_path( $pagination_template_frontend, $pagination_data ); ?> + + +
+
+
+ options( $nav_links['options'] ) + ->query_param( 'result' ) + ->render(); + ?> +
+
+ order( $order_filter )->render(); ?> +
+
+
+
+
+
+
+
+
+ $quiz_attempt ) { + $attempts = $quiz_attempt['attempts']; + $attempts_count = count( $attempts ); + + tutor_load_template( + 'dashboard.components.quiz-attempts-group', + array( + 'quiz_id' => $quiz_index, + 'quiz_title' => $quiz_attempt['quiz_title'], + 'course_title' => $quiz_attempt['course_title'], + 'attempts' => $attempts, + 'course_id' => $quiz_attempt['course_id'], + ) + ); + } + ?> +
+
+ + title( __( 'No Quiz Attempts Found', 'tutor' ) ) + ->render(); + ?> + +
+ $item_per_page ) { + Pagination::make() + ->current( $current_page ) + ->total( $quiz_attempts_count ) + ->limit( $item_per_page ) + ->prev( tutor_utils()->get_svg_icon( Icon::CHEVRON_LEFT_2 ) ) + ->next( tutor_utils()->get_svg_icon( Icon::CHEVRON_RIGHT_2 ) ) + ->render(); + } + ?> +
\ No newline at end of file diff --git a/templates/dashboard/quiz-attempts.php b/templates/dashboard/quiz-attempts.php index de67424e10..94c290fe5e 100644 --- a/templates/dashboard/quiz-attempts.php +++ b/templates/dashboard/quiz-attempts.php @@ -12,9 +12,10 @@ use Tutor\Components\ConfirmationModal; use Tutor\Components\Constants\Positions; use Tutor\Components\Constants\Size; +use Tutor\Components\Constants\Variant; use Tutor\Components\DateFilter; +use Tutor\Components\DropdownFilter; use Tutor\Components\EmptyState; -use Tutor\Components\Nav; use Tutor\Components\Pagination; use Tutor\Components\SearchFilter; use Tutor\Components\Sorting; @@ -61,10 +62,11 @@
items( array( $nav_links ) ) - ->size( Size::SMALL ) - ->render(); + DropdownFilter::make() + ->options( $nav_links['options'] ) + ->query_param( 'result' ) + ->variant( Variant::PRIMARY_SOFT ) + ->render(); ?>
From cd2cb8bdb67128a7e49144c1a3d451da3a78c662 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 27 Jan 2026 13:11:46 +0600 Subject: [PATCH 2/8] Added confirmation modal for quiz retry --- assets/src/js/frontend/dashboard/index.ts | 2 +- .../frontend/dashboard/pages/quiz-attempts.ts | 29 +++++ assets/src/js/v3/shared/utils/endpoints.ts | 3 + .../dashboard/components/quiz-attempt-row.php | 6 +- .../components/quiz-attempts-group.php | 14 +- templates/dashboard/my-quiz-attempts.php | 120 ++++++++++-------- 6 files changed, 115 insertions(+), 59 deletions(-) diff --git a/assets/src/js/frontend/dashboard/index.ts b/assets/src/js/frontend/dashboard/index.ts index 0de86f9a02..3220e28a06 100644 --- a/assets/src/js/frontend/dashboard/index.ts +++ b/assets/src/js/frontend/dashboard/index.ts @@ -52,7 +52,7 @@ const getCurrentPage = (): string => { if (path.includes('/assignments')) { return 'assignments'; } - if (path.includes('/quiz-attempts')) { + if (path.includes('/quiz-attempts') || path.includes('/my-quiz-attempts')) { return 'quiz-attempts'; } if (path.includes('/settings')) { diff --git a/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts b/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts index 39f56e2b95..88fac70b43 100644 --- a/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts +++ b/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts @@ -1,6 +1,13 @@ // Quiz Attempts Page import { type MutationState } from '@Core/ts/services/Query'; +import { tutorConfig } from '@TutorShared/config/config'; import { wpAjaxInstance } from '@TutorShared/utils/api'; +import axios from 'axios'; + +interface RetryAttempt { + quizID: string; + redirectURL: string; +} const quizAttemptsPage = () => { const query = window.TutorCore.query; @@ -8,6 +15,7 @@ const quizAttemptsPage = () => { return { query, deleteMutation: null as MutationState | null, + retryMutation: null as MutationState | null, init() { this.deleteMutation = this.query.useMutation(this.deleteAttempt, { @@ -19,6 +27,15 @@ const quizAttemptsPage = () => { window.TutorCore.toast.error(error.message || 'Failed to delete quiz attempt'); }, }); + + this.retryMutation = this.query.useMutation(this.retryAttempt, { + onSuccess: (_, payload) => { + window.location.href = payload.redirectURL; + }, + onError: (error: Error) => { + window.TutorCore.toast.error(error.message || 'Failed to retry quiz attempt'); + }, + }); }, deleteAttempt(attemptID: number) { @@ -30,6 +47,18 @@ const quizAttemptsPage = () => { async handleDeleteAttempt(attemptID: number) { await this.deleteMutation?.mutate(attemptID); }, + + retryAttempt(payload: RetryAttempt) { + return axios.postForm(payload.redirectURL, { + quiz_id: payload.quizID, + tutor_action: 'tutor_start_quiz', + _tutor_nonce: tutorConfig._tutor_nonce, + }); + }, + + async handleRetryAttempt(payload: RetryAttempt) { + await this.retryMutation?.mutate(payload); + }, }; }; diff --git a/assets/src/js/v3/shared/utils/endpoints.ts b/assets/src/js/v3/shared/utils/endpoints.ts index d946a293d6..1800e1fb67 100644 --- a/assets/src/js/v3/shared/utils/endpoints.ts +++ b/assets/src/js/v3/shared/utils/endpoints.ts @@ -181,6 +181,9 @@ const endpoints = { SAVE_BILLING_INFO: 'tutor_save_billing_info', SAVE_WITHDRAW_METHOD: 'tutor_save_withdraw_account', RESET_PASSWORD: 'tutor_profile_password_reset', + + // Quiz Attempts + START_QUIZ: 'tutor_start_quiz', } as const; export default endpoints; diff --git a/templates/dashboard/components/quiz-attempt-row.php b/templates/dashboard/components/quiz-attempt-row.php index 877aa7e6db..69477ae97c 100644 --- a/templates/dashboard/components/quiz-attempt-row.php +++ b/templates/dashboard/components/quiz-attempt-row.php @@ -70,10 +70,12 @@ ); $retry_item = array( - 'tag' => 'a', + 'tag' => 'button', 'content' => __( 'Retry', 'tutor' ), 'icon' => tutor_utils()->get_svg_icon( Icon::RELOAD ), - 'attr' => array( 'href' => get_post_permalink( $quiz_id ) ), + 'attr' => array( + '@click' => $retry_attr, + ), ); ?> diff --git a/templates/dashboard/components/quiz-attempts-group.php b/templates/dashboard/components/quiz-attempts-group.php index d162e8855f..cd58f9470e 100644 --- a/templates/dashboard/components/quiz-attempts-group.php +++ b/templates/dashboard/components/quiz-attempts-group.php @@ -24,12 +24,20 @@ $first_attempt = $attempts[0]; $remaining_attempts = array_slice( $attempts, 1 ); +$retry_attr = sprintf( + 'TutorCore.modal.showModal("tutor-retry-modal", { data: %s });', + wp_json_encode( + array( + 'quizID' => $quiz_id, + 'redirectURL' => get_post_permalink( $quiz_id ), + ) + ) +); $retry_button = Button::make()->label( __( 'Retry', 'tutor' ) ) ->icon( Icon::RELOAD ) ->size( Size::MEDIUM ) - ->tag( 'a' ) ->variant( 'primary' ) - ->attr( 'href', get_post_permalink( $quiz_id ) ); + ->attr( '@click', $retry_attr ); $attempt_info = $first_attempt['attempt_info'] ?? array(); @@ -58,6 +66,7 @@ 'attempt_id' => $first_attempt['attempt_id'] ?? 0, 'is_student' => $is_student, 'should_retry' => $should_retry, + 'retry_attr' => $retry_attr, ) ); ?> @@ -80,6 +89,7 @@ 'course_id' => $course_id, 'is_student' => $is_student, 'should_retry' => $should_retry, + 'retry_attr' => $retry_attr, ) ); ?> diff --git a/templates/dashboard/my-quiz-attempts.php b/templates/dashboard/my-quiz-attempts.php index 0fa898b718..3262801c7e 100644 --- a/templates/dashboard/my-quiz-attempts.php +++ b/templates/dashboard/my-quiz-attempts.php @@ -9,6 +9,7 @@ * @since 1.1.2 */ +use Tutor\Components\ConfirmationModal; use Tutor\Components\DropdownFilter; use Tutor\Components\EmptyState; use Tutor\Components\Pagination; @@ -43,65 +44,76 @@ $nav_links = $quiz_attempt_obj->get_quiz_attempts_nav_data( $quiz_attempts, $quiz_attempts_count, get_pagenum_link(), $result_filter ); ?> +
+ +
+
+
+ options( $nav_links['options'] ) + ->query_param( 'result' ) + ->render(); + ?> +
+
+ order( $order_filter )->render(); ?> +
+
+
+
+
+
+
+
+
+ $quiz_attempt ) { + $attempts = $quiz_attempt['attempts']; + $attempts_count = count( $attempts ); - -
-
-
+ tutor_load_template( + 'dashboard.components.quiz-attempts-group', + array( + 'quiz_id' => $quiz_index, + 'quiz_title' => $quiz_attempt['quiz_title'], + 'course_title' => $quiz_attempt['course_title'], + 'attempts' => $attempts, + 'course_id' => $quiz_attempt['course_id'], + ) + ); + } + ?> +
+
+ options( $nav_links['options'] ) - ->query_param( 'result' ) - ->render(); + EmptyState::make() + ->title( __( 'No Quiz Attempts Found', 'tutor' ) ) + ->render(); ?> + +
+ $item_per_page ) { + Pagination::make() + ->current( $current_page ) + ->total( $quiz_attempts_count ) + ->limit( $item_per_page ) + ->prev( tutor_utils()->get_svg_icon( Icon::CHEVRON_LEFT_2 ) ) + ->next( tutor_utils()->get_svg_icon( Icon::CHEVRON_RIGHT_2 ) ) + ->render(); + } + ?>
-
- order( $order_filter )->render(); ?> -
-
-
-
-
-
-
-
-
- $quiz_attempt ) { - $attempts = $quiz_attempt['attempts']; - $attempts_count = count( $attempts ); - tutor_load_template( - 'dashboard.components.quiz-attempts-group', - array( - 'quiz_id' => $quiz_index, - 'quiz_title' => $quiz_attempt['quiz_title'], - 'course_title' => $quiz_attempt['course_title'], - 'attempts' => $attempts, - 'course_id' => $quiz_attempt['course_id'], - ) - ); - } - ?> -
-
- - title( __( 'No Quiz Attempts Found', 'tutor' ) ) - ->render(); - ?> - -
$item_per_page ) { - Pagination::make() - ->current( $current_page ) - ->total( $quiz_attempts_count ) - ->limit( $item_per_page ) - ->prev( tutor_utils()->get_svg_icon( Icon::CHEVRON_LEFT_2 ) ) - ->next( tutor_utils()->get_svg_icon( Icon::CHEVRON_RIGHT_2 ) ) - ->render(); - } + ConfirmationModal::make() + ->id( 'tutor-retry-modal' ) + ->title( 'Retry Quiz ?' ) + ->message( 'Are you sure to retry this quiz attempt' ) + ->confirm_handler( 'handleRetryAttempt({...payload?.data})' ) + ->mutation_state( 'retryMutation' ) + ->render(); ?>
\ No newline at end of file From 58ba5e639d3fc72d558f8c1a472b8d36421e38d4 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 27 Jan 2026 17:20:38 +0600 Subject: [PATCH 3/8] updated retry modal content --- templates/dashboard/my-quiz-attempts.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/dashboard/my-quiz-attempts.php b/templates/dashboard/my-quiz-attempts.php index 3262801c7e..9c2a5e9040 100644 --- a/templates/dashboard/my-quiz-attempts.php +++ b/templates/dashboard/my-quiz-attempts.php @@ -110,9 +110,10 @@ id( 'tutor-retry-modal' ) - ->title( 'Retry Quiz ?' ) - ->message( 'Are you sure to retry this quiz attempt' ) + ->title( __( 'Retry This Quiz Attempt?', 'tutor' ) ) + ->message( __( 'Retrying this quiz will reset your current attempt. Your answers and score from this attempt will be lost.', 'tutor' )) ->confirm_handler( 'handleRetryAttempt({...payload?.data})' ) + ->confirm_text( __( 'Retry Quiz', 'tutor' )) ->mutation_state( 'retryMutation' ) ->render(); ?> From 4374f026755250b5becb73812bd46dcbbdcfa0ab Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Tue, 27 Jan 2026 17:31:47 +0600 Subject: [PATCH 4/8] update whitespace --- templates/dashboard/my-quiz-attempts.php | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/templates/dashboard/my-quiz-attempts.php b/templates/dashboard/my-quiz-attempts.php index 9c2a5e9040..d2b84dde5f 100644 --- a/templates/dashboard/my-quiz-attempts.php +++ b/templates/dashboard/my-quiz-attempts.php @@ -97,24 +97,24 @@ $item_per_page ) { Pagination::make() - ->current( $current_page ) - ->total( $quiz_attempts_count ) - ->limit( $item_per_page ) - ->prev( tutor_utils()->get_svg_icon( Icon::CHEVRON_LEFT_2 ) ) - ->next( tutor_utils()->get_svg_icon( Icon::CHEVRON_RIGHT_2 ) ) - ->render(); + ->current( $current_page ) + ->total( $quiz_attempts_count ) + ->limit( $item_per_page ) + ->prev( tutor_utils()->get_svg_icon( Icon::CHEVRON_LEFT_2 ) ) + ->next( tutor_utils()->get_svg_icon( Icon::CHEVRON_RIGHT_2 ) ) + ->render(); } ?>
id( 'tutor-retry-modal' ) - ->title( __( 'Retry This Quiz Attempt?', 'tutor' ) ) - ->message( __( 'Retrying this quiz will reset your current attempt. Your answers and score from this attempt will be lost.', 'tutor' )) - ->confirm_handler( 'handleRetryAttempt({...payload?.data})' ) - ->confirm_text( __( 'Retry Quiz', 'tutor' )) - ->mutation_state( 'retryMutation' ) - ->render(); + ->id( 'tutor-retry-modal' ) + ->title( __( 'Retry This Quiz Attempt?', 'tutor' ) ) + ->message( __( 'Retrying this quiz will reset your current attempt. Your answers and score from this attempt will be lost.', 'tutor' )) + ->confirm_handler( 'handleRetryAttempt({...payload?.data})' ) + ->confirm_text( __( 'Retry Quiz', 'tutor' )) + ->mutation_state( 'retryMutation' ) + ->render(); ?>
\ No newline at end of file From d20ca173c0485623f0e8786e4fc96bc680323ed2 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Thu, 29 Jan 2026 14:11:19 +0600 Subject: [PATCH 5/8] added review changes --- .../frontend/dashboard/pages/quiz-attempts.ts | 9 +- assets/src/js/v3/shared/utils/endpoints.ts | 3 +- classes/Quiz_Attempts_List.php | 261 ++++++++++++++++++ .../dashboard/components/quiz-attempt-row.php | 117 +------- .../components/quiz-attempts-group.php | 83 ++---- .../components/student-quiz-attempt-row.php | 112 ++++++++ templates/dashboard/my-quiz-attempts.php | 24 +- templates/dashboard/quiz-attempts.php | 12 +- 8 files changed, 425 insertions(+), 196 deletions(-) create mode 100644 templates/dashboard/components/student-quiz-attempt-row.php diff --git a/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts b/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts index 88fac70b43..8bd08f0ff4 100644 --- a/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts +++ b/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts @@ -2,6 +2,7 @@ import { type MutationState } from '@Core/ts/services/Query'; import { tutorConfig } from '@TutorShared/config/config'; import { wpAjaxInstance } from '@TutorShared/utils/api'; +import { convertToErrorMessage } from '@TutorShared/utils/util'; import axios from 'axios'; interface RetryAttempt { @@ -24,7 +25,7 @@ const quizAttemptsPage = () => { window.location.reload(); }, onError: (error: Error) => { - window.TutorCore.toast.error(error.message || 'Failed to delete quiz attempt'); + window.TutorCore.toast.error(convertToErrorMessage(error)); }, }); @@ -33,7 +34,7 @@ const quizAttemptsPage = () => { window.location.href = payload.redirectURL; }, onError: (error: Error) => { - window.TutorCore.toast.error(error.message || 'Failed to retry quiz attempt'); + window.TutorCore.toast.error(convertToErrorMessage(error)); }, }); }, @@ -55,10 +56,6 @@ const quizAttemptsPage = () => { _tutor_nonce: tutorConfig._tutor_nonce, }); }, - - async handleRetryAttempt(payload: RetryAttempt) { - await this.retryMutation?.mutate(payload); - }, }; }; diff --git a/assets/src/js/v3/shared/utils/endpoints.ts b/assets/src/js/v3/shared/utils/endpoints.ts index 1a01ebcd3b..8c471bba33 100644 --- a/assets/src/js/v3/shared/utils/endpoints.ts +++ b/assets/src/js/v3/shared/utils/endpoints.ts @@ -57,6 +57,7 @@ const endpoints = { QUIZ_IMPORT_DATA: 'quiz_import_data', QUIZ_EXPORT_DATA: 'quiz_export_data', DELETE_QUIZ: 'tutor_quiz_delete', + START_QUIZ: 'tutor_start_quiz', // ZOOM GET_ZOOM_MEETING_DETAILS: 'tutor_zoom_meeting_details', @@ -182,8 +183,6 @@ const endpoints = { SAVE_WITHDRAW_METHOD: 'tutor_save_withdraw_account', RESET_PASSWORD: 'tutor_profile_password_reset', - // Quiz Attempts - START_QUIZ: 'tutor_start_quiz', UPDATE_PROFILE_NOTIFICATION: 'tutor_save_notification_preference', } as const; diff --git a/classes/Quiz_Attempts_List.php b/classes/Quiz_Attempts_List.php index 6017693017..56578bd6b3 100644 --- a/classes/Quiz_Attempts_List.php +++ b/classes/Quiz_Attempts_List.php @@ -15,6 +15,11 @@ } use Tutor\Cache\QuizAttempts; +use Tutor\Components\Badge; +use Tutor\Components\Button; +use Tutor\Components\Constants\Size; +use Tutor\Components\Popover; +use Tutor\Helpers\UrlHelper; use Tutor\Models\QuizModel; /** @@ -350,4 +355,260 @@ public function get_bulk_actions() { ); return $actions; } + + /** + * Get attempt row template for quiz attempts. + * + * @since 4.0.0 + * + * @param integer $course_id the course id. + * + * @return string + */ + public function get_quiz_attempt_row_template( $course_id = 0 ): string { + $view_mode = User::VIEW_AS_STUDENT === User::get_current_view_mode(); + $is_student = ( User::is_student( get_current_user_id() ) && tutor_utils()->is_enrolled( $course_id, get_current_user_id(), false ) ) || $view_mode; + $template = $is_student ? 'dashboard.components.student-quiz-attempt-row' : 'dashboard.components.quiz-attempt-row'; + return $template; + } + + /** + * Get retry button attributes. + * + * @since 4.0.0 + * + * @param integer $quiz_id the quiz id. + * + * @return string + */ + private function get_retry_attribute( $quiz_id = 0 ): string { + $retry_attr = sprintf( + 'TutorCore.modal.showModal("tutor-retry-modal", { data: %s });', + wp_json_encode( + array( + 'quizID' => $quiz_id, + 'redirectURL' => get_post_permalink( $quiz_id ), + ) + ) + ); + + return $retry_attr; + } + + /** + * Get the quiz attempt review url. + * + * @since 4.0.0 + * + * @param array $attempt the quiz attempt. + * + * @return string + */ + public function get_review_url( $attempt = array() ): string { + return UrlHelper::add_query_params( get_pagenum_link(), array( 'view_quiz_attempt_id' => $attempt['attempt_id'] ?? 0 ) ); + } + + /** + * Render student quiz attempt retry button. + * + * @since 4.0.0 + * + * @param integer $course_id the course id. + * @param integer $quiz_id the quiz id. + * @param array $attempt the quiz attempt. + * @param integer $attempts_count the quiz attempt count. + * + * @return void + */ + public function render_retry_button( $course_id = 0, $quiz_id = 0, $attempt = array(), $attempts_count = 0 ) { + $view_mode = User::VIEW_AS_STUDENT === User::get_current_view_mode(); + $is_student = ( User::is_student( get_current_user_id() ) && tutor_utils()->is_enrolled( $course_id, get_current_user_id(), false ) ) || $view_mode; + + if ( $is_student && $this->should_retry( $attempt, $attempts_count ) ) { + Button::make() + ->label( __( 'Retry', 'tutor' ) ) + ->icon( Icon::RELOAD ) + ->size( Size::MEDIUM ) + ->variant( 'primary' ) + ->attr( '@click', $this->get_retry_attribute( $quiz_id ) ) + ->render(); + } + } + + /** + * Whether student can retry attempt or not. + * + * @since 4.0.0 + * + * @param array $attempt the quiz attempt. + * @param integer $attempts_count the quiz attempt count. + * + * @return boolean + */ + private function should_retry( $attempt = array(), $attempts_count = 0 ): bool { + $attempt_info = $attempt['attempt_info'] ?? array(); + + $should_retry = false; + + if ( tutor_utils()->count( $attempt_info ) ) { + $allowed_attempts = (int) $attempt_info['attempts_allowed'] ?? 0; + $feedback_mode = $attempt_info['feedback_mode'] ?? ''; + $should_retry = 'retry' === $feedback_mode && $attempts_count < $allowed_attempts; + } + + return $should_retry; + } + + /** + * Get kebab button for quiz attempt popover. + * + * @since 4.0.0 + * + * @return string + */ + private function get_kebab_button() { + $kebab_button = Button::make() + ->icon( Icon::THREE_DOTS_VERTICAL ) + ->attr( 'x-ref', 'trigger' ) + ->attr( '@click', 'toggle()' ) + ->attr( 'class', 'tutor-quiz-item-result-more' ) + ->variant( 'secondary' ) + ->size( Size::X_SMALL ) + ->get(); + return $kebab_button; + } + + /** + * Get quiz detail item for quiz attempt popover. + * + * @since 4.0.0 + * + * @param array $attempt the quiz attempt. + * + * @return array + */ + private function get_details_item( $attempt = array() ) { + $details_item = array( + 'tag' => 'a', + 'content' => __( 'Details', 'tutor' ), + 'icon' => tutor_utils()->get_svg_icon( Icon::RESOURCES ), + 'attr' => array( 'href' => $this->get_review_url( $attempt ) ), + ); + return $details_item; + } + + /** + * Render student quiz attempt popover. + * + * @since 4.0.0 + * + * @param array $attempt the quiz attempt. + * @param integer $attempts_count the quiz attempt count. + * @param integer $quiz_id the quiz id. + * + * @return void + */ + public function render_student_attempt_popover( $attempt = array(), $attempts_count = 0, $quiz_id = 0 ) { + // Only add retry option to the first attempt. + if ( ! $this->should_retry( $attempt, $attempts_count ) || ! $attempts_count ) { + Popover::make() + ->trigger( $this->get_kebab_button() ) + ->placement( 'bottom' ) + ->menu_item( $this->get_details_item( $attempt ) ) + ->render(); + } else { + Popover::make() + ->trigger( $this->get_kebab_button() ) + ->placement( 'bottom' ) + ->menu_item( + array( + 'tag' => 'button', + 'content' => __( 'Retry', 'tutor' ), + 'icon' => tutor_utils()->get_svg_icon( Icon::RELOAD ), + 'attr' => array( + '@click' => $this->get_retry_attribute( $quiz_id ), + ), + ) + ) + ->menu_item( $this->get_details_item( $attempt ) ) + ->render(); + } + } + + /** + * Render List Badge for quiz attempts. + * + * @since 4.0.0 + * + * @param array $attempt the quiz attempt. + * + * @return void + */ + public function render_quiz_attempt_list_badge( $attempt = array() ) { + if ( QuizModel::RESULT_PASS === $attempt['result'] ) { + Badge::make()->label( __( 'Passed', 'tutor' ) )->variant( Badge::SUCCESS )->rounded()->render(); + } elseif ( QuizModel::RESULT_PENDING === $attempt['result'] ) { + Badge::make()->label( __( 'Pending', 'tutor' ) )->variant( Badge::WARNING )->rounded()->render(); + } else { + Badge::make()->label( 'Failed' )->variant( Badge::ERROR )->rounded()->render(); + } + } + + /** + * Render quiz attempt mobile view buttons. + * + * @since 4.0.0 + * + * @param array $attempt the quiz attempt. + * + * @return void + */ + public function render_quiz_attempt_buttons( $attempt = array() ) { + Button::make() + ->label( __( 'Details', 'tutor' ) ) + ->icon( Icon::RESOURCES, 'left', 20, 20 ) + ->size( Size::MEDIUM ) + ->tag( 'a' ) + ->attr( 'href', $this->get_review_url( $attempt ) ) + ->variant( 'primary' ) + ->render(); + + Button::make() + ->label( __( 'Delete', 'tutor' ) ) + ->icon( Icon::DELETE_2, 'left', 20, 20 ) + ->size( Size::MEDIUM ) + ->attr( '@click', sprintf( 'TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });', $attempt['attempt_id'] ?? 0 ) ) + ->variant( 'secondary' ) + ->render(); + } + + /** + * Render quiz attempt popover for instructor quiz attempt list. + * + * @since 4.0.0 + * + * @param array $attempt the quiz attempt. + * + * @return void + */ + public function render_quiz_attempt_popover( $attempt = array() ) { + Popover::make() + ->trigger( $this->get_kebab_button() ) + ->placement( 'bottom' ) + ->menu_item( $this->get_details_item( $attempt ) ) + ->menu_item( + array( + 'tag' => 'button', + 'content' => __( 'Delete', 'tutor' ), + 'icon' => tutor_utils()->get_svg_icon( Icon::DELETE_2 ), + 'attr' => array( + '@click' => sprintf( + 'hide(); TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });', + $attempt['attempt_id'] ?? 0 + ), + ), + ) + ) + ->render(); + } } diff --git a/templates/dashboard/components/quiz-attempt-row.php b/templates/dashboard/components/quiz-attempt-row.php index 69477ae97c..43af6617aa 100644 --- a/templates/dashboard/components/quiz-attempt-row.php +++ b/templates/dashboard/components/quiz-attempt-row.php @@ -9,13 +9,8 @@ * @since 4.0.0 */ -use Tutor\Components\Badge; -use Tutor\Components\Button; -use Tutor\Components\Constants\Size; -use Tutor\Components\Popover; use Tutor\Components\PreviewTrigger; use TUTOR\Icon; -use Tutor\Models\QuizModel; if ( empty( $attempt ) ) { return; @@ -26,57 +21,6 @@ $show_course = $show_course ?? false; $attempt_number = $attempt_number ?? null; $attempts_count = $attempts_count ?? 0; -$badge = Badge::make()->label( 'Failed' )->variant( Badge::ERROR )->rounded(); -$kebab_button = Button::make() - ->icon( Icon::THREE_DOTS_VERTICAL ) - ->attr( 'x-ref', 'trigger' ) - ->attr( '@click', 'toggle()' ) - ->attr( 'class', 'tutor-quiz-item-result-more' ) - ->variant( 'secondary' ) - ->size( Size::X_SMALL ) - ->get(); - -$review_url = add_query_arg( array( 'view_quiz_attempt_id' => $attempt_id ), get_pagenum_link() ); -$click_attr = sprintf( 'hide(); TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });', $attempt_id ); -$delete_attr = sprintf( 'TutorCore.modal.showModal("tutor-quiz-attempt-delete-modal", { attemptID: %d });', $attempt_id ); -$details_button = Button::make()->label( __( 'Details', 'tutor' ) ) - ->icon( Icon::RESOURCES, 'left', 20, 20 ) - ->size( Size::MEDIUM ) - ->tag( 'a' ) - ->attr( 'href', $review_url ) - ->variant( 'primary' ); - -$delete_button = Button::make()->label( __( 'Delete', 'tutor' ) ) - ->icon( Icon::DELETE_2, 'left', 20, 20 ) - ->size( Size::MEDIUM ) - ->attr( '@click', $delete_attr ) - ->variant( 'secondary' ); - -$details_item = array( - 'tag' => 'a', - 'content' => __( 'Details', 'tutor' ), - 'icon' => tutor_utils()->get_svg_icon( Icon::RESOURCES ), - 'attr' => array( 'href' => $review_url ), -); - -$delete_item = array( - 'tag' => 'a', - 'content' => __( 'Delete', 'tutor' ), - 'icon' => tutor_utils()->get_svg_icon( Icon::DELETE_2 ), - 'attr' => array( - 'href' => '#', - '@click' => $click_attr, - ), -); - -$retry_item = array( - 'tag' => 'button', - 'content' => __( 'Retry', 'tutor' ), - 'icon' => tutor_utils()->get_svg_icon( Icon::RELOAD ), - 'attr' => array( - '@click' => $retry_attr, - ), -); ?>
@@ -101,31 +45,17 @@ render_svg_icon( Icon::CHEVRON_DOWN, 18, 18 ); ?> - -
- -
-
- -
- -
-
-
@@ -138,9 +68,7 @@
-
-
@@ -170,50 +98,13 @@
label( __( 'Passed', 'tutor' ) ) - ->variant( Badge::SUCCESS ) - ->render(); - } elseif ( QuizModel::RESULT_PENDING === $attempt['result'] ) { - $badge - ->label( __( 'Pending', 'tutor' ) ) - ->variant( Badge::WARNING ) - ->render(); - } else { - $badge->render(); - } - - if ( $is_student && ! $should_retry ) { - Popover::make() - ->trigger( $kebab_button ) - ->placement( 'bottom' ) - ->menu_item( - $details_item - ) - ->render(); - } else { - Popover::make() - ->trigger( $kebab_button ) - ->placement( 'bottom' ) - ->menu_item( - $is_student ? $retry_item : $details_item - ) - ->menu_item( - $is_student ? $details_item : $delete_item - ) - ->render(); - } - - + $quiz_attempt_obj->render_quiz_attempt_list_badge( $attempt ); + $quiz_attempt_obj->render_quiz_attempt_popover( $attempt ); ?> - +
-
- render(); ?> - render(); ?> + render_quiz_attempt_buttons( $attempt ); ?>
- diff --git a/templates/dashboard/components/quiz-attempts-group.php b/templates/dashboard/components/quiz-attempts-group.php index cd58f9470e..76eaabe685 100644 --- a/templates/dashboard/components/quiz-attempts-group.php +++ b/templates/dashboard/components/quiz-attempts-group.php @@ -9,14 +9,6 @@ * @since 4.0.0 */ -use Tutor\Components\Button; -use Tutor\Components\Constants\Size; -use TUTOR\Icon; -use TUTOR\User; - -$attempts_count = tutor_utils()->count( $attempts ); -$is_student = User::is_student( get_current_user_id() ) && tutor_utils()->is_enrolled( $course_id, get_current_user_id(), false ); - if ( empty( $attempts ) ) { return; } @@ -24,49 +16,23 @@ $first_attempt = $attempts[0]; $remaining_attempts = array_slice( $attempts, 1 ); -$retry_attr = sprintf( - 'TutorCore.modal.showModal("tutor-retry-modal", { data: %s });', - wp_json_encode( - array( - 'quizID' => $quiz_id, - 'redirectURL' => get_post_permalink( $quiz_id ), - ) - ) -); -$retry_button = Button::make()->label( __( 'Retry', 'tutor' ) ) - ->icon( Icon::RELOAD ) - ->size( Size::MEDIUM ) - ->variant( 'primary' ) - ->attr( '@click', $retry_attr ); - -$attempt_info = $first_attempt['attempt_info'] ?? array(); - -$should_retry = false; - -if ( tutor_utils()->count( $attempt_info ) ) { - $allowed_attempts = (int) $attempt_info['attempts_allowed'] ?? 0; - $feedback_mode = $attempt_info['feedback_mode'] ?? ''; - $should_retry = 'retry' === $feedback_mode && $attempts_count < $allowed_attempts; -} ?>
get_quiz_attempt_row_template( $course_id ), array( - 'attempt' => $first_attempt, - 'quiz_title' => $quiz_title, - 'course_title' => $course_title, - 'course_id' => $course_id, - 'show_quiz_title' => true, - 'show_course' => true, - 'quiz_id' => $quiz_id, - 'attempts_count' => $attempts_count, - 'attempt_id' => $first_attempt['attempt_id'] ?? 0, - 'is_student' => $is_student, - 'should_retry' => $should_retry, - 'retry_attr' => $retry_attr, + 'attempt' => $first_attempt, + 'quiz_title' => $quiz_title, + 'course_title' => $course_title, + 'course_id' => $course_id, + 'show_quiz_title' => true, + 'show_course' => true, + 'quiz_id' => $quiz_id, + 'attempts_count' => $attempts_count, + 'attempt_id' => $first_attempt['attempt_id'] ?? 0, + 'quiz_attempt_obj' => $quiz_attempt_obj, ) ); ?> @@ -80,16 +46,14 @@ $attempt ) : ?> get_quiz_attempt_row_template( $course_id ), array( - 'attempt' => $attempt, - 'attempt_number' => count( $remaining_attempts ) - $key, - 'quiz_id' => $quiz_id, - 'attempt_id' => $attempt['attempt_id'] ?? 0, - 'course_id' => $course_id, - 'is_student' => $is_student, - 'should_retry' => $should_retry, - 'retry_attr' => $retry_attr, + 'attempt' => $attempt, + 'attempt_number' => count( $remaining_attempts ) - $key, + 'quiz_id' => $quiz_id, + 'attempt_id' => $attempt['attempt_id'] ?? 0, + 'course_id' => $course_id, + 'quiz_attempt_obj' => $quiz_attempt_obj, ) ); ?> @@ -97,9 +61,14 @@
-
- render(); ?> + render_retry_button( + $course_id, + $quiz_id, + $first_attempt, + $attempts_count + ); + ?>
-
diff --git a/templates/dashboard/components/student-quiz-attempt-row.php b/templates/dashboard/components/student-quiz-attempt-row.php new file mode 100644 index 0000000000..8ee0d910b9 --- /dev/null +++ b/templates/dashboard/components/student-quiz-attempt-row.php @@ -0,0 +1,112 @@ + + * @link https://themeum.com + * @since 4.0.0 + */ + +use Tutor\Components\PreviewTrigger; +use TUTOR\Icon; + +if ( empty( $attempt ) ) { + return; +} + +$show_quiz_title = $show_quiz_title ?? false; +$show_course = $show_course ?? false; +$attempt_number = $attempt_number ?? null; +$attempts_count = $attempts_count ?? 0; + +?> +
+
+ +
+
+ +
+ 1 ) : ?> + + + + +
+ + + +
+
+ +
+ +
+ + +
+ + id( $course_id ?? 0 ) + ->render() + ?> +
+ +
+ +
+ +
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+ +
+ render_svg_icon( Icon::STOPWATCH, 20, 20, array( 'class' => 'tutor-icon-secondary' ) ); ?> + +
+ +
+ render_quiz_attempt_list_badge( $attempt ); + $quiz_attempt_obj->render_student_attempt_popover( $attempt, $attempts_count, $quiz_id ); + ?> + +
+
diff --git a/templates/dashboard/my-quiz-attempts.php b/templates/dashboard/my-quiz-attempts.php index d2b84dde5f..b94261c307 100644 --- a/templates/dashboard/my-quiz-attempts.php +++ b/templates/dashboard/my-quiz-attempts.php @@ -14,7 +14,6 @@ use Tutor\Components\EmptyState; use Tutor\Components\Pagination; use Tutor\Components\Sorting; -use TUTOR\Icon; use TUTOR\Input; use Tutor\Models\QuizModel; use TUTOR\Quiz_Attempts_List; @@ -75,11 +74,13 @@ tutor_load_template( 'dashboard.components.quiz-attempts-group', array( - 'quiz_id' => $quiz_index, - 'quiz_title' => $quiz_attempt['quiz_title'], - 'course_title' => $quiz_attempt['course_title'], - 'attempts' => $attempts, - 'course_id' => $quiz_attempt['course_id'], + 'quiz_id' => $quiz_index, + 'quiz_title' => $quiz_attempt['quiz_title'], + 'course_title' => $quiz_attempt['course_title'], + 'attempts' => $attempts, + 'course_id' => $quiz_attempt['course_id'], + 'quiz_attempt_obj' => $quiz_attempt_obj, + 'attempts_count' => $attempts_count, ) ); } @@ -93,27 +94,24 @@ ->render(); ?> -
$item_per_page ) { Pagination::make() ->current( $current_page ) ->total( $quiz_attempts_count ) ->limit( $item_per_page ) - ->prev( tutor_utils()->get_svg_icon( Icon::CHEVRON_LEFT_2 ) ) - ->next( tutor_utils()->get_svg_icon( Icon::CHEVRON_RIGHT_2 ) ) + ->attr( 'class', 'tutor-p-6' ) ->render(); } ?> -
id( 'tutor-retry-modal' ) ->title( __( 'Retry This Quiz Attempt?', 'tutor' ) ) - ->message( __( 'Retrying this quiz will reset your current attempt. Your answers and score from this attempt will be lost.', 'tutor' )) - ->confirm_handler( 'handleRetryAttempt({...payload?.data})' ) - ->confirm_text( __( 'Retry Quiz', 'tutor' )) + ->message( __( 'Retrying this quiz will reset your current attempt. Your answers and score from this attempt will be lost.', 'tutor' ) ) + ->confirm_handler( 'retryMutation?.mutate({...payload?.data})' ) + ->confirm_text( __( 'Retry Quiz', 'tutor' ) ) ->mutation_state( 'retryMutation' ) ->render(); ?> diff --git a/templates/dashboard/quiz-attempts.php b/templates/dashboard/quiz-attempts.php index 94c290fe5e..7cdd4bf3fc 100644 --- a/templates/dashboard/quiz-attempts.php +++ b/templates/dashboard/quiz-attempts.php @@ -108,11 +108,13 @@ tutor_load_template( 'dashboard.components.quiz-attempts-group', array( - 'quiz_id' => $quiz_index, - 'quiz_title' => $quiz_attempt['quiz_title'], - 'course_title' => $quiz_attempt['course_title'], - 'attempts' => $attempts, - 'course_id' => $quiz_attempt['course_id'], + 'quiz_id' => $quiz_index, + 'quiz_title' => $quiz_attempt['quiz_title'], + 'course_title' => $quiz_attempt['course_title'], + 'attempts' => $attempts, + 'course_id' => $quiz_attempt['course_id'], + 'quiz_attempt_obj' => $quiz_attempt_obj, + 'attempts_count' => $attempts_count, ) ); } From 59656709ef48b3ae66525afdb9aedbff769973c9 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Thu, 29 Jan 2026 15:05:43 +0600 Subject: [PATCH 6/8] revert old changes for utils --- classes/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Utils.php b/classes/Utils.php index 78c41c55d3..bbc41e75bd 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -8548,7 +8548,7 @@ public function is_tutor_dashboard( $subpage = null ) { * * @return boolean */ - public function is_tutor_frontend_dashboard( $subpage = '' ) { + public function is_tutor_frontend_dashboard( $subpage = null ) { global $wp_query; if ( $wp_query->is_page ) { $dashboard_page = $this->array_get( 'tutor_dashboard_page', $wp_query->query_vars ); From 05aa2c15b65a9cb210f4d81a25a9d1640842dbad Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Fri, 30 Jan 2026 13:09:43 +0600 Subject: [PATCH 7/8] Added changes --- assets/src/js/v3/shared/utils/endpoints.ts | 1 - classes/Quiz_Attempts_List.php | 26 ++++++++++++++++------ templates/dashboard/my-quiz-attempts.php | 15 +++++-------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/assets/src/js/v3/shared/utils/endpoints.ts b/assets/src/js/v3/shared/utils/endpoints.ts index 8c471bba33..bfb1676900 100644 --- a/assets/src/js/v3/shared/utils/endpoints.ts +++ b/assets/src/js/v3/shared/utils/endpoints.ts @@ -182,7 +182,6 @@ const endpoints = { SAVE_BILLING_INFO: 'tutor_save_billing_info', SAVE_WITHDRAW_METHOD: 'tutor_save_withdraw_account', RESET_PASSWORD: 'tutor_profile_password_reset', - UPDATE_PROFILE_NOTIFICATION: 'tutor_save_notification_preference', } as const; diff --git a/classes/Quiz_Attempts_List.php b/classes/Quiz_Attempts_List.php index 56578bd6b3..b919f96443 100644 --- a/classes/Quiz_Attempts_List.php +++ b/classes/Quiz_Attempts_List.php @@ -356,6 +356,22 @@ public function get_bulk_actions() { return $actions; } + /** + * Check whether to show instructor or student quiz attempt. + * + * @since 4.0.0 + * + * @param integer $course_id the course id. + * + * @return bool + */ + private function check_is_student( $course_id = 0 ): bool { + $is_student_view = User::VIEW_AS_STUDENT === User::get_current_view_mode(); + $is_student = tutor_utils()->is_enrolled( $course_id, get_current_user_id(), false ) && $is_student_view; + + return $is_student; + } + /** * Get attempt row template for quiz attempts. * @@ -366,9 +382,8 @@ public function get_bulk_actions() { * @return string */ public function get_quiz_attempt_row_template( $course_id = 0 ): string { - $view_mode = User::VIEW_AS_STUDENT === User::get_current_view_mode(); - $is_student = ( User::is_student( get_current_user_id() ) && tutor_utils()->is_enrolled( $course_id, get_current_user_id(), false ) ) || $view_mode; - $template = $is_student ? 'dashboard.components.student-quiz-attempt-row' : 'dashboard.components.quiz-attempt-row'; + $template = $this->check_is_student( $course_id ) ? 'dashboard.components.student-quiz-attempt-row' + : 'dashboard.components.quiz-attempt-row'; return $template; } @@ -421,10 +436,7 @@ public function get_review_url( $attempt = array() ): string { * @return void */ public function render_retry_button( $course_id = 0, $quiz_id = 0, $attempt = array(), $attempts_count = 0 ) { - $view_mode = User::VIEW_AS_STUDENT === User::get_current_view_mode(); - $is_student = ( User::is_student( get_current_user_id() ) && tutor_utils()->is_enrolled( $course_id, get_current_user_id(), false ) ) || $view_mode; - - if ( $is_student && $this->should_retry( $attempt, $attempts_count ) ) { + if ( $this->check_is_student( $course_id ) && $this->should_retry( $attempt, $attempts_count ) ) { Button::make() ->label( __( 'Retry', 'tutor' ) ) ->icon( Icon::RELOAD ) diff --git a/templates/dashboard/my-quiz-attempts.php b/templates/dashboard/my-quiz-attempts.php index b94261c307..f130672680 100644 --- a/templates/dashboard/my-quiz-attempts.php +++ b/templates/dashboard/my-quiz-attempts.php @@ -95,16 +95,13 @@ ?> $item_per_page ) { - Pagination::make() - ->current( $current_page ) - ->total( $quiz_attempts_count ) - ->limit( $item_per_page ) - ->attr( 'class', 'tutor-p-6' ) - ->render(); - } + Pagination::make() + ->current( $current_page ) + ->total( $quiz_attempts_count ) + ->limit( $item_per_page ) + ->attr( 'class', 'tutor-p-6' ) + ->render(); ?> - id( 'tutor-retry-modal' ) From 7c9ca8eb228e1de7b21ac758e5f331c3c8a56f46 Mon Sep 17 00:00:00 2001 From: Anindra Das Bivas Date: Fri, 30 Jan 2026 17:14:27 +0600 Subject: [PATCH 8/8] removed tutor-h4 style on quiz_attempts --- assets/src/scss/frontend/dashboard/_quiz-attempts.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/scss/frontend/dashboard/_quiz-attempts.scss b/assets/src/scss/frontend/dashboard/_quiz-attempts.scss index cecac75da1..68dfc38351 100644 --- a/assets/src/scss/frontend/dashboard/_quiz-attempts.scss +++ b/assets/src/scss/frontend/dashboard/_quiz-attempts.scss @@ -4,7 +4,7 @@ @use '@Core/scss/tokens' as *; @use '@Core/scss/mixins' as *; -.tutor-h4 { +.tutor-quiz-attempts-mobile-heading { @include tutor-breakpoint-up(sm) { display: none; }