feat: Implement Assessment Builder UI (Step 10) - Basic layout, AJAX setup, drag-and-drop
This commit is contained in:
parent
d6a429fbdb
commit
2ea343b65f
4 changed files with 543 additions and 1 deletions
|
|
@ -9,9 +9,10 @@ function quiztech_theme_enqueue_scripts() {
|
|||
'template-manage-questions.php',
|
||||
'template-manage-credits.php',
|
||||
'template-view-results.php',
|
||||
'template-assessment-builder.php', // Add Assessment Builder template
|
||||
];
|
||||
|
||||
// Check if the current page is using one of our templates
|
||||
// Check if the current page is using one of our general Quiztech templates
|
||||
$is_quiztech_template = false;
|
||||
foreach ( $quiztech_templates as $template ) {
|
||||
if ( is_page_template( $template ) ) {
|
||||
|
|
@ -46,6 +47,33 @@ function quiztech_theme_enqueue_scripts() {
|
|||
'job_added_success' => esc_html__( 'Job added successfully!', 'quiztech' ),
|
||||
]);
|
||||
}
|
||||
|
||||
// --- Enqueue script specifically for Assessment Builder ---
|
||||
if ( is_page_template( 'template-assessment-builder.php' ) ) {
|
||||
$builder_script_path = get_stylesheet_directory() . '/js/quiztech-assessment-builder.js';
|
||||
$builder_script_url = get_stylesheet_directory_uri() . '/js/quiztech-assessment-builder.js';
|
||||
$builder_version = file_exists( $builder_script_path ) ? filemtime( $builder_script_path ) : '1.0';
|
||||
|
||||
wp_enqueue_script(
|
||||
'quiztech-builder-script', // Unique handle
|
||||
$builder_script_url,
|
||||
array( 'jquery', 'jquery-ui-sortable' ), // Depends on jQuery and Sortable for drag/drop later
|
||||
$builder_version,
|
||||
true // Load in footer
|
||||
);
|
||||
|
||||
// Localize data specifically for the builder script
|
||||
wp_localize_script( 'quiztech-builder-script', 'quiztechBuilderData', [
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'fetch_nonce' => wp_create_nonce( 'quiztech_fetch_library_questions_action' ), // TODO: Verify action name in AJAX handler
|
||||
'save_nonce' => wp_create_nonce( 'quiztech_save_assessment_action' ), // TODO: Verify action name in AJAX handler
|
||||
'error_generic' => esc_html__( 'An error occurred. Please try again.', 'quiztech' ),
|
||||
'loading_questions' => esc_html__( 'Loading questions...', 'quiztech' ),
|
||||
'saving_assessment' => esc_html__( 'Saving assessment...', 'quiztech' ),
|
||||
'assessment_saved' => esc_html__( 'Assessment saved successfully!', 'quiztech' ),
|
||||
// Add more localized strings as needed
|
||||
]);
|
||||
}
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'quiztech_theme_enqueue_scripts' );
|
||||
|
||||
|
|
|
|||
314
js/quiztech-assessment-builder.js
Normal file
314
js/quiztech-assessment-builder.js
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
jQuery(document).ready(function($) {
|
||||
console.log('Assessment Builder Script Loaded'); // Debugging
|
||||
|
||||
// --- Cache DOM Elements ---
|
||||
const $libraryList = $('#library-questions-list');
|
||||
const $currentList = $('#current-assessment-questions');
|
||||
const $assessmentTitleInput = $('#assessment-title');
|
||||
const $totalCostSpan = $('#assessment-total-cost');
|
||||
const $saveButton = $('#save-assessment-button');
|
||||
const $saveSpinner = $saveButton.siblings('.spinner');
|
||||
const $saveStatus = $('#save-status');
|
||||
|
||||
// --- State ---
|
||||
let currentAssessmentQuestions = []; // Array to hold IDs of questions in the right pane
|
||||
let currentAssessmentId = null; // Store the ID if editing an existing assessment (TODO: Load this if editing)
|
||||
|
||||
// --- Functions ---
|
||||
|
||||
/**
|
||||
* Updates the total cost display based on the questions currently in the assessment list.
|
||||
*/
|
||||
function updateTotalCost() {
|
||||
let totalCost = 0;
|
||||
$currentList.find('.selected-question-item').each(function() {
|
||||
totalCost += parseInt($(this).data('cost') || 0);
|
||||
});
|
||||
$totalCostSpan.text(totalCost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal `currentAssessmentQuestions` array based on the current DOM order.
|
||||
*/
|
||||
function updateStateFromDOM() {
|
||||
currentAssessmentQuestions = $currentList.find('.selected-question-item').map(function() {
|
||||
return $(this).data('question-id');
|
||||
}).get(); // .get() converts jQuery object to array
|
||||
console.log('Updated State:', currentAssessmentQuestions); // Debugging
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches questions for the library pane via AJAX.
|
||||
* TODO: Add parameters for search, filters, pagination.
|
||||
*/
|
||||
function fetchLibraryQuestions(page = 1, search = '') {
|
||||
console.log('Fetching library questions...');
|
||||
$libraryList.html('<p>' + quiztechBuilderData.loading_questions + '</p>'); // Show loading indicator
|
||||
|
||||
$.post(quiztechBuilderData.ajax_url, {
|
||||
action: 'quiztech_fetch_library_questions',
|
||||
// nonce: quiztechBuilderData.fetch_nonce, // TODO: Implement nonce verification in PHP
|
||||
page: page,
|
||||
search: search
|
||||
})
|
||||
.done(function(response) {
|
||||
if (response.success && response.data.questions) {
|
||||
renderLibraryQuestions(response.data.questions);
|
||||
// TODO: Render pagination controls using response.data.pagination
|
||||
} else {
|
||||
$libraryList.html('<p class="error">' + (response.data.message || quiztechBuilderData.error_generic) + '</p>');
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
$libraryList.html('<p class="error">' + quiztechBuilderData.error_generic + '</p>');
|
||||
})
|
||||
.always(function() {
|
||||
// Hide loading indicator if needed
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the fetched questions into the library list container.
|
||||
* @param {Array} questions Array of question objects {id, title, type, cost}.
|
||||
*/
|
||||
function renderLibraryQuestions(questions) {
|
||||
$libraryList.empty(); // Clear previous content or loading indicator
|
||||
if (questions.length === 0) {
|
||||
$libraryList.html('<p>' + 'No questions found.' + '</p>'); // TODO: Localize
|
||||
return;
|
||||
}
|
||||
|
||||
questions.forEach(function(q) {
|
||||
// Check if question is already in the current assessment
|
||||
const isAdded = currentAssessmentQuestions.includes(q.id);
|
||||
const buttonHtml = isAdded
|
||||
? '<button class="add-question-to-assessment" data-question-id="' + q.id + '" disabled>Added</button>' // TODO: Localize
|
||||
: '<button class="add-question-to-assessment" data-question-id="' + q.id + '">Add</button>'; // TODO: Localize
|
||||
|
||||
const itemHtml = `
|
||||
<div class="library-question-item" data-question-id="${q.id}" data-cost="${q.cost}">
|
||||
<span>
|
||||
<strong>${q.title || 'Untitled'}</strong> (Type: ${q.type || 'N/A'}) [Cost: ${q.cost || 0}] <!-- TODO: Localize -->
|
||||
</span>
|
||||
${buttonHtml}
|
||||
</div>
|
||||
`;
|
||||
$libraryList.append(itemHtml);
|
||||
});
|
||||
|
||||
// --- Initialize Draggable on newly rendered library items ---
|
||||
initializeDraggable();
|
||||
// --- --- --- --- ---
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes draggable functionality on library items.
|
||||
*/
|
||||
function initializeDraggable() {
|
||||
$libraryList.find('.library-question-item').draggable({
|
||||
connectToSortable: '#current-assessment-questions',
|
||||
helper: 'clone',
|
||||
revert: 'invalid',
|
||||
start: function(event, ui) {
|
||||
ui.helper.css('width', $(this).width()); // Maintain width during drag
|
||||
ui.helper.addClass('dragging-helper'); // Optional: for styling the helper
|
||||
},
|
||||
stop: function(event, ui) {
|
||||
ui.helper.removeClass('dragging-helper');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to find question data in the currently rendered library list.
|
||||
* Note: This is inefficient if the library is paginated. A better approach
|
||||
* would be to store added question details in a separate object.
|
||||
* @param {number} questionId
|
||||
* @returns {object|null} Object with title, cost etc. or null if not found in current view.
|
||||
*/
|
||||
function findQuestionDataInLibrary(questionId) {
|
||||
const $item = $libraryList.find(`.library-question-item[data-question-id="${questionId}"]`);
|
||||
if ($item.length) {
|
||||
return {
|
||||
id: questionId,
|
||||
title: $item.find('strong').text(),
|
||||
cost: $item.data('cost')
|
||||
// Extract type if needed
|
||||
};
|
||||
}
|
||||
return null; // Not found in the current library view
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the 'Add' buttons in the library list, disabling those for questions
|
||||
* already present in the current assessment.
|
||||
*/
|
||||
function updateLibraryButtons() {
|
||||
$libraryList.find('.add-question-to-assessment').each(function() {
|
||||
const $button = $(this);
|
||||
const questionId = $button.data('question-id');
|
||||
if (currentAssessmentQuestions.includes(questionId)) {
|
||||
$button.prop('disabled', true).text('Added'); // TODO: Localize
|
||||
} else {
|
||||
$button.prop('disabled', false).text('Add'); // TODO: Localize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current assessment (title and question IDs) via AJAX.
|
||||
*/
|
||||
function saveAssessment() {
|
||||
console.log('Saving assessment...');
|
||||
$saveSpinner.css('visibility', 'visible');
|
||||
$saveButton.prop('disabled', true);
|
||||
$saveStatus.empty().removeClass('success error');
|
||||
|
||||
const assessmentTitle = $assessmentTitleInput.val();
|
||||
if (!assessmentTitle) {
|
||||
alert('Assessment title cannot be empty.'); // TODO: Use a nicer notification
|
||||
$saveSpinner.css('visibility', 'hidden');
|
||||
$saveButton.prop('disabled', false);
|
||||
return;
|
||||
}
|
||||
|
||||
$.post(quiztechBuilderData.ajax_url, {
|
||||
action: 'quiztech_save_assessment',
|
||||
// nonce: quiztechBuilderData.save_nonce, // TODO: Implement nonce verification in PHP
|
||||
assessment_id: currentAssessmentId, // Will be null for new assessments
|
||||
title: assessmentTitle,
|
||||
question_ids: currentAssessmentQuestions
|
||||
})
|
||||
.done(function(response) {
|
||||
if (response.success && response.data.assessment_id) {
|
||||
currentAssessmentId = response.data.assessment_id; // Store the ID after saving
|
||||
$saveStatus.text(quiztechBuilderData.assessment_saved).addClass('success');
|
||||
// Optionally redirect or update UI further
|
||||
} else {
|
||||
$saveStatus.text(response.data.message || quiztechBuilderData.error_generic).addClass('error');
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
$saveStatus.text(quiztechBuilderData.error_generic).addClass('error');
|
||||
})
|
||||
.always(function() {
|
||||
$saveSpinner.css('visibility', 'hidden');
|
||||
$saveButton.prop('disabled', false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// --- Event Handlers ---
|
||||
|
||||
// Add question from library to current assessment (Handles manual click - less relevant with drag/drop but keep for now)
|
||||
$libraryList.on('click', '.add-question-to-assessment', function() {
|
||||
const $button = $(this);
|
||||
const questionId = $button.data('question-id');
|
||||
const $item = $button.closest('.library-question-item');
|
||||
|
||||
if (!currentAssessmentQuestions.includes(questionId)) {
|
||||
// Clone the item, modify it, and append to the current list
|
||||
const $clonedItem = $item.clone();
|
||||
$clonedItem.removeClass('library-question-item').addClass('selected-question-item');
|
||||
$clonedItem.find('.add-question-to-assessment').replaceWith(
|
||||
`<button class="remove-question-from-assessment" data-question-id="${questionId}">Remove</button>` // TODO: Localize
|
||||
);
|
||||
|
||||
// Remove placeholder if it exists
|
||||
$currentList.find('p').remove();
|
||||
|
||||
$currentList.append($clonedItem);
|
||||
|
||||
// Update state and UI
|
||||
updateStateFromDOM();
|
||||
updateTotalCost();
|
||||
updateLibraryButtons();
|
||||
}
|
||||
});
|
||||
|
||||
// Remove question from current assessment
|
||||
$currentList.on('click', '.remove-question-from-assessment', function() {
|
||||
const $button = $(this);
|
||||
const questionId = $button.data('question-id');
|
||||
$button.closest('.selected-question-item').remove(); // Remove item from DOM
|
||||
|
||||
// Update state and UI
|
||||
updateStateFromDOM();
|
||||
updateTotalCost();
|
||||
updateLibraryButtons(); // Re-enable the 'Add' button in the library
|
||||
|
||||
// Add placeholder if list becomes empty
|
||||
if ($currentList.children().length === 0) {
|
||||
$currentList.html('<p>' + 'No questions added yet.' + '</p>'); // TODO: Localize
|
||||
}
|
||||
});
|
||||
|
||||
// Save assessment button click
|
||||
$saveButton.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
saveAssessment();
|
||||
});
|
||||
|
||||
// TODO: Add handlers for search/filter/pagination controls when implemented.
|
||||
// --- Initialize Sortable ---
|
||||
$currentList.sortable({
|
||||
placeholder: "ui-state-highlight", // Class for placeholder styling
|
||||
axis: 'y', // Allow vertical sorting only
|
||||
update: function(event, ui) {
|
||||
// This fires when sorting stops *within* the list OR when an item is received
|
||||
console.log('Sortable Updated');
|
||||
updateStateFromDOM();
|
||||
updateTotalCost(); // Recalculate cost based on new order/items
|
||||
},
|
||||
receive: function(event, ui) {
|
||||
// This fires specifically when a draggable is dropped onto the sortable
|
||||
console.log('Sortable Received');
|
||||
const questionId = ui.item.data('question-id');
|
||||
const $originalItem = ui.item; // This is the helper clone that becomes the list item
|
||||
|
||||
// Check for duplicates based on the state *before* this item was potentially added by 'update'
|
||||
const existingIds = $currentList.find('.selected-question-item').map(function() {
|
||||
return $(this).data('question-id');
|
||||
}).get();
|
||||
let count = 0;
|
||||
existingIds.forEach(id => { if (id === questionId) count++; });
|
||||
|
||||
if (count > 1) { // If the dropped item creates a duplicate
|
||||
console.log('Duplicate detected, cancelling drop');
|
||||
ui.sender.sortable('cancel'); // Cancel the drop/receive operation
|
||||
$originalItem.remove(); // Remove the duplicate item that was briefly added
|
||||
alert('This question is already in the assessment.'); // TODO: Nicer notification
|
||||
return; // Stop further processing for this event
|
||||
}
|
||||
|
||||
// Transform the received item (clone) into a selected item
|
||||
$originalItem.removeClass('library-question-item dragging-helper').addClass('selected-question-item');
|
||||
$originalItem.find('.add-question-to-assessment').replaceWith(
|
||||
`<button class="remove-question-from-assessment" data-question-id="${questionId}">Remove</button>` // TODO: Localize
|
||||
);
|
||||
$originalItem.css({ width: '', height: '' }); // Reset helper styles
|
||||
|
||||
// Remove placeholder if it exists
|
||||
$currentList.find('p').remove();
|
||||
|
||||
// Update state and UI (update event will also fire, but good to be explicit)
|
||||
updateStateFromDOM();
|
||||
updateTotalCost();
|
||||
updateLibraryButtons(); // Disable the 'Add' button in the library
|
||||
}
|
||||
}).disableSelection(); // Prevent text selection while dragging
|
||||
|
||||
// --- Initial Load ---
|
||||
fetchLibraryQuestions(); // Load initial questions into the library (will also init draggable)
|
||||
// renderCurrentAssessment(); // Don't render initially, sortable handles it
|
||||
|
||||
// Add placeholder initially if list is empty
|
||||
if ($currentList.children().length === 0) {
|
||||
$currentList.html('<p>' + 'No questions added yet.' + '</p>'); // TODO: Localize
|
||||
}
|
||||
|
||||
});
|
||||
108
style.css
108
style.css
|
|
@ -8,3 +8,111 @@
|
|||
Version: 0.1
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/* Assessment Builder Styles */
|
||||
.quiztech-assessment-builder-area .entry-content {
|
||||
max-width: none; /* Allow content to take full width if needed */
|
||||
}
|
||||
|
||||
#assessment-builder-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap; /* Allow wrapping on smaller screens */
|
||||
gap: 20px; /* Space between panes */
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#assessment-builder-library {
|
||||
flex: 1 1 300px; /* Flex-grow, flex-shrink, flex-basis */
|
||||
min-width: 280px; /* Minimum width before wrapping */
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
background-color: #f9f9f9;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#assessment-builder-current {
|
||||
flex: 2 1 500px; /* Takes up more space */
|
||||
min-width: 400px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#library-questions-list,
|
||||
#current-assessment-questions {
|
||||
min-height: 200px; /* Ensure panes have some height */
|
||||
max-height: 400px; /* Limit height and allow scrolling if needed */
|
||||
overflow-y: auto; /* Add scrollbar if content exceeds max-height */
|
||||
border: 1px dashed #eee;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Add some basic styling for list items (placeholders) */
|
||||
.library-question-item,
|
||||
.selected-question-item {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.library-question-item:last-child,
|
||||
.selected-question-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#assessment-summary {
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* Responsive adjustments (optional example) */
|
||||
@media (max-width: 768px) {
|
||||
#assessment-builder-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
#assessment-builder-library,
|
||||
#assessment-builder-current {
|
||||
flex-basis: auto; /* Reset basis when stacked */
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Drag and Drop Styles */
|
||||
.ui-sortable-placeholder {
|
||||
border: 1px dashed #ccc;
|
||||
background-color: #f0f0f0;
|
||||
height: 40px; /* Adjust height to match item height */
|
||||
margin-bottom: 8px; /* Match item margin/padding */
|
||||
visibility: visible !important; /* Ensure placeholder is visible */
|
||||
}
|
||||
|
||||
.dragging-helper {
|
||||
opacity: 0.8;
|
||||
border: 1px dashed #aaa;
|
||||
background-color: #fff;
|
||||
z-index: 9999; /* Ensure helper is on top */
|
||||
}
|
||||
|
||||
/* Style for selected items in the right pane */
|
||||
#current-assessment-questions .selected-question-item {
|
||||
cursor: move; /* Indicate items are draggable/sortable */
|
||||
background-color: #fefefe;
|
||||
}
|
||||
|
||||
/* Optional: Style adjustments for library items */
|
||||
#assessment-builder-library .library-question-item {
|
||||
cursor: grab;
|
||||
}
|
||||
#assessment-builder-library .library-question-item:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
|
|
|||
92
template-assessment-builder.php
Normal file
92
template-assessment-builder.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
/**
|
||||
* Template Name: Assessment Builder
|
||||
*
|
||||
* This template is used for the page where Quiz Managers can build and edit Assessments.
|
||||
*
|
||||
* @package Quiztech
|
||||
*/
|
||||
|
||||
// Ensure the user is logged in and has the appropriate capability.
|
||||
// Using 'manage_options' as a placeholder capability for Quiz Managers.
|
||||
// Replace with a more specific capability like 'manage_quiztech_assessments' if defined.
|
||||
if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
|
||||
// Redirect to login page or show an error message.
|
||||
wp_safe_redirect( wp_login_url( get_permalink() ) );
|
||||
exit;
|
||||
// Alternatively, display an error message:
|
||||
// wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'quiztech' ), 403 );
|
||||
}
|
||||
|
||||
get_header(); ?>
|
||||
|
||||
<div id="primary" class="content-area quiztech-assessment-builder-area">
|
||||
<main id="main" class="site-main">
|
||||
|
||||
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
|
||||
<header class="entry-header">
|
||||
<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
|
||||
</header><!-- .entry-header -->
|
||||
|
||||
<div class="entry-content">
|
||||
|
||||
<div id="assessment-builder-container" style="display: flex; gap: 20px;">
|
||||
|
||||
<!-- Left Pane: Question Library -->
|
||||
<div id="assessment-builder-library" style="flex: 1; border: 1px solid #ccc; padding: 15px;">
|
||||
<h2><?php esc_html_e( 'Question Library', 'quiztech' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Search/filter controls will go here.', 'quiztech' ); ?></p>
|
||||
<div id="library-questions-list">
|
||||
<?php esc_html_e( 'Questions will be loaded here via AJAX.', 'quiztech' ); ?>
|
||||
<!-- Example Question Item Structure (for planning)
|
||||
<div class="library-question-item" data-question-id="123">
|
||||
<strong>Question Title</strong> (Type: Multiple Choice) [Cost: 1 credit]
|
||||
<button class="add-question-to-assessment">Add</button>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<hr>
|
||||
<h3><?php esc_html_e( 'Create New Question', 'quiztech' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Form for creating a new question will go here.', 'quiztech' ); ?></p>
|
||||
</div>
|
||||
|
||||
<!-- Right Pane: Current Assessment -->
|
||||
<div id="assessment-builder-current" style="flex: 2; border: 1px solid #ccc; padding: 15px;">
|
||||
<h2><?php esc_html_e( 'Current Assessment', 'quiztech' ); ?></h2>
|
||||
<p>
|
||||
<label for="assessment-title"><?php esc_html_e( 'Assessment Title:', 'quiztech' ); ?></label><br>
|
||||
<input type="text" id="assessment-title" name="assessment_title" style="width: 100%;">
|
||||
</p>
|
||||
<div id="current-assessment-questions">
|
||||
<h3><?php esc_html_e( 'Selected Questions', 'quiztech' ); ?></h3>
|
||||
<?php esc_html_e( 'Questions added from the library will appear here.', 'quiztech' ); ?>
|
||||
<!-- Example Selected Question Item Structure (for planning)
|
||||
<div class="selected-question-item" data-question-id="123">
|
||||
<strong>Question Title</strong> (Type: Multiple Choice) [Cost: 1 credit]
|
||||
<button class="remove-question-from-assessment">Remove</button>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<hr>
|
||||
<div id="assessment-summary">
|
||||
<strong><?php esc_html_e( 'Total Estimated Cost:', 'quiztech' ); ?></strong>
|
||||
<span id="assessment-total-cost">0</span> <?php esc_html_e( 'credits', 'quiztech' ); ?>
|
||||
</div>
|
||||
<p>
|
||||
<button id="save-assessment-button" class="button button-primary"><?php esc_html_e( 'Save Assessment', 'quiztech' ); ?></button>
|
||||
<span class="spinner" style="float: none; vertical-align: middle;"></span>
|
||||
<span id="save-status" style="margin-left: 10px;"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div><!-- #assessment-builder-container -->
|
||||
|
||||
</div><!-- .entry-content -->
|
||||
|
||||
</article><!-- #post-<?php the_ID(); ?> -->
|
||||
|
||||
</main><!-- #main -->
|
||||
</div><!-- #primary -->
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
Loading…
Add table
Reference in a new issue