diff --git a/assets/core/ts/components/form.ts b/assets/core/ts/components/form.ts index aa69e15c46..3dfbc1d848 100644 --- a/assets/core/ts/components/form.ts +++ b/assets/core/ts/components/form.ts @@ -375,7 +375,7 @@ export const form = (config: FormControlConfig & { id?: string } = {}) => { const isCheckbox = type === 'checkbox'; const isFile = type === 'file'; - const defaultValue = this.values[name] ?? (isCheckbox ? element?.checked ?? false : ''); + const defaultValue = this.values[name] ?? (isCheckbox ? (element?.checked ?? false) : ''); this.fields[name] = { name, 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..8bd08f0ff4 100644 --- a/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts +++ b/assets/src/js/frontend/dashboard/pages/quiz-attempts.ts @@ -1,6 +1,14 @@ // Quiz Attempts Page 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 { + quizID: string; + redirectURL: string; +} const quizAttemptsPage = () => { const query = window.TutorCore.query; @@ -8,6 +16,7 @@ const quizAttemptsPage = () => { return { query, deleteMutation: null as MutationState | null, + retryMutation: null as MutationState | null, init() { this.deleteMutation = this.query.useMutation(this.deleteAttempt, { @@ -16,7 +25,16 @@ 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)); + }, + }); + + this.retryMutation = this.query.useMutation(this.retryAttempt, { + onSuccess: (_, payload) => { + window.location.href = payload.redirectURL; + }, + onError: (error: Error) => { + window.TutorCore.toast.error(convertToErrorMessage(error)); }, }); }, @@ -30,6 +48,14 @@ 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, + }); + }, }; }; diff --git a/assets/src/js/v3/shared/utils/endpoints.ts b/assets/src/js/v3/shared/utils/endpoints.ts index c5dab03d99..bfb1676900 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', @@ -181,7 +182,7 @@ 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' + UPDATE_PROFILE_NOTIFICATION: 'tutor_save_notification_preference', } as const; export default endpoints; diff --git a/assets/src/scss/frontend/dashboard/_quiz-attempts.scss b/assets/src/scss/frontend/dashboard/_quiz-attempts.scss index 8e3c680c1a..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; } @@ -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/Quiz_Attempts_List.php b/classes/Quiz_Attempts_List.php index 6017693017..b919f96443 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,272 @@ 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. + * + * @since 4.0.0 + * + * @param integer $course_id the course id. + * + * @return string + */ + public function get_quiz_attempt_row_template( $course_id = 0 ): string { + $template = $this->check_is_student( $course_id ) ? '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 ) { + if ( $this->check_is_student( $course_id ) && $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/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..43af6617aa 100644 --- a/templates/dashboard/components/quiz-attempt-row.php +++ b/templates/dashboard/components/quiz-attempt-row.php @@ -9,14 +9,8 @@ * @since 4.0.0 */ -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; -use Tutor\Models\QuizModel; if ( empty( $attempt ) ) { return; @@ -27,39 +21,17 @@ $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' ); - ?>
- +
-
+
+ +
1 ) : ?>
@@ -128,49 +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(); - } - - - 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 ), - ) - ) - ->menu_item( - array( - 'tag' => 'a', - 'content' => __( 'Delete', 'tutor' ), - 'icon' => tutor_utils()->get_svg_icon( Icon::DELETE_2 ), - 'attr' => array( - 'href' => '#', - '@click' => $click_attr, - ), - ) - ) - ->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 fa858fa9d4..76eaabe685 100644 --- a/templates/dashboard/components/quiz-attempts-group.php +++ b/templates/dashboard/components/quiz-attempts-group.php @@ -9,8 +9,6 @@ * @since 4.0.0 */ -$attempts_count = tutor_utils()->count( $attempts ); - if ( empty( $attempts ) ) { return; } @@ -18,23 +16,23 @@ $first_attempt = $attempts[0]; $remaining_attempts = array_slice( $attempts, 1 ); - ?>
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, + '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, ) ); ?> @@ -48,17 +46,29 @@ $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, + '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, ) ); ?>
+ +
+ 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 520ea3c4e3..f130672680 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\ConfirmationModal; +use Tutor\Components\DropdownFilter; +use Tutor\Components\EmptyState; +use Tutor\Components\Pagination; +use Tutor\Components\Sorting; 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,92 @@ 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'], + 'quiz_attempt_obj' => $quiz_attempt_obj, + 'attempts_count' => $attempts_count, + ) + ); + } + ?> +
+
+ + title( __( 'No Quiz Attempts Found', 'tutor' ) ) + ->render(); + ?> + + current( $current_page ) + ->total( $quiz_attempts_count ) + ->limit( $item_per_page ) + ->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( 'retryMutation?.mutate({...payload?.data})' ) + ->confirm_text( __( 'Retry Quiz', 'tutor' ) ) + ->mutation_state( 'retryMutation' ) + ->render(); + ?> +
\ No newline at end of file diff --git a/templates/dashboard/quiz-attempts.php b/templates/dashboard/quiz-attempts.php index de67424e10..7cdd4bf3fc 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(); ?>
@@ -106,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, ) ); }