DecisionTree/templates/admin/edit_node.html

324 lines
18 KiB
HTML

{% extends 'base.html' %}
{% macro render_node_tree(node) %}
{% if node %}
<ul class="list-unstyled ms-4">
{% if node.yes_node %}
<li>
<div class="d-flex align-items-center mb-2">
<span class="badge bg-success me-2">Yes</span>
<a href="{{ url_for('admin.edit_node', node_id=node.yes_node.id) }}" class="text-decoration-none">
{{ node.yes_node.question }}
</a>
{% if node.yes_node.is_terminal %}
<span class="badge bg-secondary ms-2">Terminal</span>
{% endif %}
</div>
{{ render_node_tree(node.yes_node) }}
</li>
{% endif %}
{% if node.no_node %}
<li>
<div class="d-flex align-items-center mb-2">
<span class="badge bg-danger me-2">No</span>
<a href="{{ url_for('admin.edit_node', node_id=node.no_node.id) }}" class="text-decoration-none">
{{ node.no_node.question }}
</a>
{% if node.no_node.is_terminal %}
<span class="badge bg-secondary ms-2">Terminal</span>
{% endif %}
</div>
{{ render_node_tree(node.no_node) }}
</li>
{% endif %}
</ul>
{% endif %}
{% endmacro %}
{% block content %}
<div class="container-fluid mt-4">
<div class="row">
<!-- Left column: Tree visualization -->
<div class="col-md-6">
<div class="card shadow mb-4">
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Decision Tree Visualization</h6>
</div>
<div class="card-body">
<div id="tree-visualization" class="overflow-auto" style="height: 500px;"></div>
<input type="hidden" id="tree-data" value="{{ tree_json }}">
</div>
</div>
<!-- Debug data section -->
<div class="mt-3">
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#debugData">
Debug Tree Data
</button>
<div class="collapse mt-2" id="debugData">
<div class="card card-body">
<pre id="debug-output" style="max-height: 200px; overflow: auto;"></pre>
</div>
</div>
</div>
</div>
<!-- Right column: Node edit form -->
<div class="col-md-6">
<div class="card shadow mb-4">
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Edit Node</h6>
<div>
<a href="{{ url_for('admin.edit_decision_tree', product_id=product.id) if product else '#' }}" class="btn btn-sm btn-outline-primary">
Back to Tree
</a>
</div>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label for="question" class="form-label">Question</label>
<input type="text" class="form-control" id="question" name="question" value="{{ node.question }}" required>
</div>
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea class="form-control" id="content" name="content" rows="5">{{ node.content }}</textarea>
<small class="text-muted">This content will be shown to the user when they reach this node.</small>
</div>
<!-- Media content section -->
<div class="card mb-3">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" id="mediaTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="images-tab" data-bs-toggle="tab" data-bs-target="#images-content" type="button" role="tab">Images</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="youtube-tab" data-bs-toggle="tab" data-bs-target="#youtube-content" type="button" role="tab">YouTube Video</button>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" id="mediaTabContent">
<!-- Images Tab -->
<div class="tab-pane fade show active" id="images-content" role="tabpanel" aria-labelledby="images-tab">
<!-- Current Images -->
{% if node.images %}
<h6>Current Images</h6>
<div class="row mb-3">
{% for image in node.images|sort(attribute='display_order') %}
<div class="col-md-4 mb-3">
<div class="card">
<img src="{{ image.image_url }}" class="card-img-top" alt="Node image">
<div class="card-body">
{% if image.caption %}
<p class="card-text small">{{ image.caption }}</p>
{% endif %}
<div class="d-flex justify-content-between align-items-center">
<div class="input-group input-group-sm">
<span class="input-group-text">Order</span>
<input type="number" class="form-control" name="image_order_{{ image.id }}" value="{{ image.display_order }}" min="0">
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remove_image_{{ image.id }}" id="remove_image_{{ image.id }}">
<label class="form-check-label" for="remove_image_{{ image.id }}">Remove</label>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Upload New Image -->
<h6>Upload New Image</h6>
<div class="mb-3">
<label for="image_upload" class="form-label">Select Image</label>
<input type="file" class="form-control" id="image_upload" name="image_upload" accept="image/*">
</div>
<div class="mb-3">
<label for="image_caption" class="form-label">Caption (optional)</label>
<input type="text" class="form-control" id="image_caption" name="image_caption" placeholder="Enter a caption for the image">
</div>
<div id="image-preview" class="mt-3"></div>
</div>
<!-- YouTube Tab -->
<div class="tab-pane fade" id="youtube-content" role="tabpanel" aria-labelledby="youtube-tab">
<div class="mb-3">
<label for="youtube_url" class="form-label">YouTube URL</label>
<input type="url" class="form-control" id="youtube_url" name="youtube_url" value="{{ node.youtube_url or '' }}" placeholder="https://www.youtube.com/watch?v=...">
<div class="form-text">Enter a YouTube video URL to embed in this node.</div>
</div>
{% if node.youtube_embed_url %}
<div class="ratio ratio-16x9 mt-3">
<iframe src="{{ node.youtube_embed_url }}" title="YouTube video" allowfullscreen></iframe>
</div>
{% else %}
<div id="youtube-preview"></div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="is_terminal" name="is_terminal" {% if node.is_terminal %}checked{% endif %}>
<label class="form-check-label" for="is_terminal">Terminal Node</label>
<small class="d-block text-muted">Check this if this node is an endpoint (no further questions).</small>
</div>
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
</div>
</div>
<!-- Add child nodes section -->
<div class="row">
<div class="col-md-6">
<div class="card shadow mb-4 add-branch-form">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-success">Add 'Yes' Branch</h6>
</div>
<div class="card-body">
{% if node.yes_node %}
<p>Already has a 'Yes' branch:</p>
<a href="{{ url_for('admin.edit_node', node_id=node.yes_node.id) }}" class="btn btn-outline-success btn-sm">
Edit 'Yes' Node
</a>
{% else %}
<form method="POST" action="{{ url_for('admin.add_child_node', parent_id=node.id, direction='yes') }}">
<div class="mb-3">
<label for="yes_question" class="form-label">Question</label>
<input type="text" class="form-control" id="yes_question" name="question" placeholder="Enter question for 'Yes' branch">
</div>
<div class="mb-3">
<label for="yes_content" class="form-label">Content</label>
<textarea class="form-control" id="yes_content" name="content" rows="3" placeholder="Enter content for 'Yes' branch"></textarea>
</div>
<button type="submit" class="btn btn-success btn-sm">Add 'Yes' Node</button>
</form>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card shadow mb-4 add-branch-form">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-danger">Add 'No' Branch</h6>
</div>
<div class="card-body">
{% if node.no_node %}
<p>Already has a 'No' branch:</p>
<a href="{{ url_for('admin.edit_node', node_id=node.no_node.id) }}" class="btn btn-outline-danger btn-sm">
Edit 'No' Node
</a>
{% else %}
<form method="POST" action="{{ url_for('admin.add_child_node', parent_id=node.id, direction='no') }}">
<div class="mb-3">
<label for="no_question" class="form-label">Question</label>
<input type="text" class="form-control" id="no_question" name="question" placeholder="Enter question for 'No' branch">
</div>
<div class="mb-3">
<label for="no_content" class="form-label">Content</label>
<textarea class="form-control" id="no_content" name="content" rows="3" placeholder="Enter content for 'No' branch"></textarea>
</div>
<button type="submit" class="btn btn-danger btn-sm">Add 'No' Node</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
/* Add any additional styles needed for the tree visualization */
</style>
<!-- Include D3.js for tree visualization -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="{{ url_for('static', filename='js/tree-visualizer.js') }}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const debugOutput = document.getElementById('debug-output');
const treeData = document.getElementById('tree-data');
if (debugOutput && treeData) {
try {
const data = JSON.parse(treeData.value);
debugOutput.textContent = JSON.stringify(data, null, 2);
} catch (e) {
debugOutput.textContent = "Error parsing tree data: " + e.message;
}
}
// Add event listener for terminal checkbox
const terminalCheckbox = document.getElementById('is_terminal');
if (terminalCheckbox) {
terminalCheckbox.addEventListener('change', function() {
const addBranchForms = document.querySelectorAll('.add-branch-form');
addBranchForms.forEach(form => {
form.style.display = this.checked ? 'none' : 'block';
});
});
// Trigger the change event to set initial state
if (terminalCheckbox.checked) {
const addBranchForms = document.querySelectorAll('.add-branch-form');
addBranchForms.forEach(form => {
form.style.display = 'none';
});
}
}
// YouTube URL preview
const youtubeUrlInput = document.getElementById('youtube_url');
if (youtubeUrlInput) {
youtubeUrlInput.addEventListener('input', function() {
const previewContainer = document.getElementById('youtube-preview');
if (previewContainer) {
const url = youtubeUrlInput.value;
if (url && isValidYouTubeUrl(url)) {
const embedUrl = getYouTubeEmbedUrl(url);
previewContainer.innerHTML = `
<div class="ratio ratio-16x9 mt-3">
<iframe src="${embedUrl}" title="YouTube video" allowfullscreen></iframe>
</div>
`;
} else {
previewContainer.innerHTML = '';
}
}
});
}
// Helper function to validate YouTube URL
function isValidYouTubeUrl(url) {
const regex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/;
return regex.test(url);
}
// Helper function to convert YouTube URL to embed URL
function getYouTubeEmbedUrl(url) {
let videoId = '';
// Extract video ID from various YouTube URL formats
if (url.includes('youtube.com/watch')) {
const urlParams = new URLSearchParams(new URL(url).search);
videoId = urlParams.get('v');
} else if (url.includes('youtu.be/')) {
videoId = url.split('youtu.be/')[1].split('?')[0];
}
return `https://www.youtube.com/embed/${videoId}`;
}
});
</script>
{% endblock %}