forked from UKSOURCE/cms.hailearning.edu.vn
first commit
This commit is contained in:
134
views/admin/level/create.ejs
Normal file
134
views/admin/level/create.ejs
Normal file
@@ -0,0 +1,134 @@
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mt-4 mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-0" style="color: var(--primary-dark);">Create New Level Type</h1>
|
||||
<p class="text-muted mb-0">Add a new level type to the system</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/admin/level" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i>Back to Level Management
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 mx-auto">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<form action="/admin/level/create" method="POST">
|
||||
<div class="mb-4">
|
||||
<label for="newType" class="form-label fw-medium">New Level Type Name</label>
|
||||
<input type="text" class="form-control" id="newType" name="newType" required
|
||||
placeholder="Enter new level type (e.g. foundation, diploma, etc.)">
|
||||
<div class="form-text">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
This will be used in URLs and as an identifier in the system.
|
||||
Use only lowercase letters, numbers, and hyphens.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-lightbulb me-2"></i>
|
||||
<strong>Tips:</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
<li>Choose a descriptive, concise name for the level type</li>
|
||||
<li>Avoid using special characters or spaces</li>
|
||||
<li>Use hyphens instead of spaces if needed (e.g. "pre-master" instead of "pre master")</li>
|
||||
<li>The system will automatically convert your entry to lowercase and replace spaces with hyphens</li>
|
||||
<li>Special characters will be removed automatically</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<strong>Sample Data Included:</strong>
|
||||
<p class="mb-1 mt-2">When you create a new level type, it will be pre-populated with sample data including:</p>
|
||||
<ul class="mb-0">
|
||||
<li>Banner information</li>
|
||||
<li>Overview with paragraphs</li>
|
||||
<li>Contact and social information</li>
|
||||
<li>Entry requirements</li>
|
||||
<li>Quick links and action buttons</li>
|
||||
<li>"Why Study" section with 4 sample items</li>
|
||||
</ul>
|
||||
<p class="mt-2 mb-0">You can easily edit all this content after creation.</p>
|
||||
</div>
|
||||
|
||||
<% if (existingTypes && existingTypes.length > 0) { %>
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-medium">Existing Level Types</label>
|
||||
<div class="list-group">
|
||||
<% existingTypes.forEach(type => { %>
|
||||
<div class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong><%= type.charAt(0).toUpperCase() + type.slice(1) %></strong>
|
||||
</div>
|
||||
<a href="/admin/level?type=<%= type %>" class="btn btn-sm btn-outline-primary">
|
||||
<i class="fas fa-edit me-1"></i>Edit
|
||||
</a>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="d-grid gap-2 mt-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-plus-circle me-1"></i>Create Level Type
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal tùy chỉnh -->
|
||||
<div id="customModal" class="custom-modal">
|
||||
<div class="custom-modal-content">
|
||||
<div class="custom-modal-header">
|
||||
<h5 class="custom-modal-title">Notification</h5>
|
||||
<button type="button" class="custom-modal-close">×</button>
|
||||
</div>
|
||||
<div class="custom-modal-body">
|
||||
<p id="modalMessage">Content of the notification will appear here.</p>
|
||||
</div>
|
||||
<div class="custom-modal-footer">
|
||||
<button type="button" class="btn btn-primary custom-modal-ok">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import custom modal CSS -->
|
||||
<link rel="stylesheet" href="/css/custom-modal.css">
|
||||
|
||||
<script>
|
||||
// Xử lý form
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Khởi tạo modal tùy chỉnh
|
||||
CustomModal.init('customModal');
|
||||
|
||||
const form = document.querySelector('form');
|
||||
|
||||
// Xử lý form submit
|
||||
form.addEventListener('submit', function(e) {
|
||||
const typeInput = document.getElementById('newType');
|
||||
const typeValue = typeInput.value.trim();
|
||||
|
||||
// Kiểm tra giá trị nhập vào
|
||||
if (!typeValue) {
|
||||
e.preventDefault();
|
||||
CustomModal.alert('Please enter a name for the level type.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Kiểm tra định dạng
|
||||
if (/[^a-zA-Z0-9\s\-]/.test(typeValue)) {
|
||||
e.preventDefault();
|
||||
CustomModal.alert('The level type name should only contain letters, numbers, hyphens, and spaces.');
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
743
views/admin/level/index.ejs
Normal file
743
views/admin/level/index.ejs
Normal file
@@ -0,0 +1,743 @@
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mt-4 mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-0" style="color: var(--primary-dark);">
|
||||
<% const formattedType = currentType.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); %>
|
||||
<%= formattedType %> Level
|
||||
</h1>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="levelTypeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<%= formattedType %>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="levelTypeDropdown">
|
||||
<% levelTypes.forEach(type => { %>
|
||||
<li>
|
||||
<a class="dropdown-item <%= type === currentType ? 'active' : '' %>" href="/admin/level?type=<%= type %>">
|
||||
<%= type.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') %>
|
||||
</a>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Thêm nút tạo mới type -->
|
||||
<a href="/admin/level/create" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-1"></i>Add New Level Type
|
||||
</a>
|
||||
|
||||
<!-- Nút xóa type hiện tại (chỉ hiển thị với non-default types) -->
|
||||
<% if (!['undergraduate', 'postgraduate', 'pre-university'].includes(currentType)) { %>
|
||||
<button type="button" class="btn btn-outline-danger" data-custom-modal="open" data-type="<%= currentType %>">
|
||||
<i class="fas fa-trash me-1"></i>Delete This Level Type
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<form id="levelForm" action="/admin/level/update?type=<%= currentType %>" method="POST" class="content-with-fixed-buttons">
|
||||
<input type="hidden" name="type" value="<%= currentType %>">
|
||||
<input type="hidden" name="banner" id="bannerJson">
|
||||
<input type="hidden" name="overview" id="overviewJson">
|
||||
<input type="hidden" name="requirements" id="requirementsJson">
|
||||
<input type="hidden" name="action_buttons" id="actionbuttonsJson">
|
||||
<input type="hidden" name="why_study" id="whystudyJson">
|
||||
|
||||
<!-- Level Type and Brochure Field -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Level Type</label>
|
||||
<input type="text" class="form-control" value="<%= currentType.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') %>" readonly>
|
||||
<div class="form-text text-muted">This is the level type identifier. It cannot be changed.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Brochure Link</label>
|
||||
<input type="text" class="form-control" id="levelBrochure" name="brochure" value="<%= data.brochure || '#' %>" placeholder="Enter brochure URL or # for no link">
|
||||
<div class="form-text text-muted">Link to the level brochure. Use # if no brochure is available.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Tabs -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" href="#banner" role="tab">
|
||||
<i class="fas fa-image me-2"></i>Banner
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#overview" role="tab">
|
||||
<i class="fas fa-info-circle me-2"></i>Overview
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#requirements" role="tab">
|
||||
<i class="fas fa-list-check me-2"></i>Requirements
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#actions" role="tab">
|
||||
<i class="fas fa-link me-2"></i>Action Buttons
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" href="#why" role="tab">
|
||||
<i class="fas fa-question-circle me-2"></i>Why Study
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- Banner Tab -->
|
||||
<div class="tab-pane fade show active" id="banner" role="tabpanel">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Banner Image</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6 d-flex align-items-baseline">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="bannerImage" value="<%= data.banner.image %>">
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="bannerImage">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<!-- Ảnh preview cho banner -->
|
||||
<div class="image-preview mt-2">
|
||||
<% if (data.banner.image) { %>
|
||||
<img src="<%= data.banner.image %>" alt="Banner preview" class="img-thumbnail" style="height:200px;width:100%;object-fit:cover;">
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="bannerTitle" value="<%= data.banner.title %>">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Text</label>
|
||||
<textarea class="form-control" id="bannerText" rows="3"><%= data.banner.text %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Overview Tab -->
|
||||
<div class="tab-pane fade" id="overview" role="tabpanel">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="overviewTitle" value="<%= data.overview.title %>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Paragraphs</label>
|
||||
<div id="overviewParagraphs">
|
||||
<% (data.overview.paragraphs || []).forEach((paragraph, index) => { %>
|
||||
<div class="mb-3 d-flex gap-2">
|
||||
<textarea class="form-control" rows="3"><%= paragraph %></textarea>
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeParagraph(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary mt-2" onclick="addParagraph('overviewParagraphs')">
|
||||
<i class="fas fa-plus me-1"></i>Add Paragraph
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Contact Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="contactTitle" value="<%= data.overview.contact_info.title %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Subtitle</label>
|
||||
<input type="text" class="form-control" id="contactSubtitle" value="<%= data.overview.contact_info.subtitle %>">
|
||||
</div>
|
||||
<div id="contactItems">
|
||||
<% (data.overview.contact_info.items || []).forEach((item, index) => { %>
|
||||
<div class="mb-3">
|
||||
<input type="text" class="form-control" value="<%= item.text %>">
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Social Info -->
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Social Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="socialTitle" value="<%= data.overview.social_info.title %>">
|
||||
</div>
|
||||
<div id="socialLinks">
|
||||
<% (data.overview.social_info.social_links || []).forEach((link, index) => { %>
|
||||
<div class="mb-3 border p-3">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Image</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="<%= link.image %>">
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">URL</label>
|
||||
<input type="text" class="form-control" value="<%= link.url %>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Alt Text</label>
|
||||
<input type="text" class="form-control" value="<%= link.alt %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h6>Apply Button</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Text</label>
|
||||
<input type="text" class="form-control" id="applyButtonText" value="<%= data.overview.social_info.apply_button.text %>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">URL</label>
|
||||
<input type="text" class="form-control" id="applyButtonUrl" value="<%= data.overview.social_info.apply_button.url %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Requirements Tab -->
|
||||
<div class="tab-pane fade" id="requirements" role="tabpanel">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="requirementsTitle" value="<%= data.requirements.title %>">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Requirements List</label>
|
||||
<div id="requirementItems">
|
||||
<% (data.requirements.items || []).forEach((item, index) => { %>
|
||||
<div class="mb-3 d-flex gap-2">
|
||||
<input type="text" class="form-control" value="<%= item %>">
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeItem(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary mt-2" onclick="addRequirementItem()">
|
||||
<i class="fas fa-plus me-1"></i>Add Requirement
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons Tab -->
|
||||
<div class="tab-pane fade" id="actions" role="tabpanel">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="actionButtonsTitle" value="<%= data.action_buttons.title %>">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Buttons</label>
|
||||
<div id="actionButtonsList">
|
||||
<% (data.action_buttons.buttons || []).forEach((button, index) => { %>
|
||||
<div class="mb-3 border p-3">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Text</label>
|
||||
<input type="text" class="form-control" value="<%= button.text %>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Link</label>
|
||||
<input type="text" class="form-control" value="<%= button.link %>">
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeActionButton(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary mt-2" onclick="addActionButton()">
|
||||
<i class="fas fa-plus me-1"></i>Add Button
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Why Study Tab -->
|
||||
<div class="tab-pane fade" id="why" role="tabpanel">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="whyStudyTitle" value="<%= data.why_study.title %>">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Items</label>
|
||||
<div id="whyStudyItems">
|
||||
<% (data.why_study.items || []).forEach((item, index) => { %>
|
||||
<div class="mb-3 border p-3">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Number</label>
|
||||
<input type="text" class="form-control" value="<%= item.number %>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Title</label>
|
||||
<input type="text" class="form-control" value="<%= item.title %>">
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Text</label>
|
||||
<textarea class="form-control" rows="2"><%= item.text %></textarea>
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeWhyStudyItem(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary mt-2" onclick="addWhyStudyItem()">
|
||||
<i class="fas fa-plus me-1"></i>Add Item
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fixed bottom buttons -->
|
||||
<div class="fixed-bottom-buttons">
|
||||
<button type="reset" class="btn btn-secondary">
|
||||
<i class="fas fa-undo me-1"></i>Reset
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save me-1"></i>Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thêm input file ẩn để sử dụng cho upload trực tiếp -->
|
||||
<input type="file" id="directImageUpload" style="display: none;">
|
||||
<input type="hidden" id="currentImageType" name="imageType">
|
||||
<input type="hidden" id="currentTargetInput" name="targetInput">
|
||||
|
||||
<script>
|
||||
// Helper function to create JSON from form data with validation
|
||||
function createJsonFromForm() {
|
||||
try {
|
||||
// Banner
|
||||
const bannerJson = {
|
||||
image: document.getElementById('bannerImage')?.value || '',
|
||||
title: document.getElementById('bannerTitle')?.value || '',
|
||||
text: document.getElementById('bannerText')?.value || ''
|
||||
};
|
||||
document.getElementById('bannerJson').value = JSON.stringify(bannerJson);
|
||||
|
||||
// Overview
|
||||
const overviewJson = {
|
||||
title: document.getElementById('overviewTitle')?.value || '',
|
||||
paragraphs: Array.from(document.querySelectorAll('#overviewParagraphs textarea')).map(ta => ta.value || ''),
|
||||
contact_info: {
|
||||
title: document.getElementById('contactTitle')?.value || '',
|
||||
subtitle: document.getElementById('contactSubtitle')?.value || '',
|
||||
items: Array.from(document.querySelectorAll('#contactItems input')).map(input => ({
|
||||
text: input.value || ''
|
||||
}))
|
||||
},
|
||||
social_info: {
|
||||
title: document.getElementById('socialTitle')?.value || '',
|
||||
social_links: Array.from(document.querySelectorAll('#socialLinks .mb-3')).map(div => ({
|
||||
image: div.querySelectorAll('input')[0]?.value || '',
|
||||
url: div.querySelectorAll('input')[1]?.value || '',
|
||||
alt: div.querySelectorAll('input')[2]?.value || ''
|
||||
})),
|
||||
apply_button: {
|
||||
text: document.getElementById('applyButtonText')?.value || '',
|
||||
url: document.getElementById('applyButtonUrl')?.value || ''
|
||||
}
|
||||
}
|
||||
};
|
||||
document.getElementById('overviewJson').value = JSON.stringify(overviewJson);
|
||||
|
||||
// Requirements
|
||||
const requirementsJson = {
|
||||
title: document.getElementById('requirementsTitle')?.value || '',
|
||||
items: Array.from(document.querySelectorAll('#requirementItems input')).map(input => input.value || '')
|
||||
};
|
||||
document.getElementById('requirementsJson').value = JSON.stringify(requirementsJson);
|
||||
|
||||
// Action Buttons
|
||||
const actionButtonsJson = {
|
||||
title: document.getElementById('actionButtonsTitle')?.value || '',
|
||||
buttons: Array.from(document.querySelectorAll('#actionButtonsList .mb-3')).map(div => ({
|
||||
text: div.querySelectorAll('input')[0]?.value || '',
|
||||
link: div.querySelectorAll('input')[1]?.value || ''
|
||||
}))
|
||||
};
|
||||
document.getElementById('actionbuttonsJson').value = JSON.stringify(actionButtonsJson);
|
||||
|
||||
// Why Study
|
||||
const whyStudyJson = {
|
||||
title: document.getElementById('whyStudyTitle')?.value || '',
|
||||
items: Array.from(document.querySelectorAll('#whyStudyItems .mb-3')).map(div => ({
|
||||
number: div.querySelectorAll('input')[0]?.value || '',
|
||||
title: div.querySelectorAll('input')[1]?.value || '',
|
||||
text: div.querySelector('textarea')?.value || ''
|
||||
}))
|
||||
};
|
||||
document.getElementById('whystudyJson').value = JSON.stringify(whyStudyJson);
|
||||
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error creating JSON:', error);
|
||||
if (window.toastManager) {
|
||||
window.toastManager.error('Error preparing form data: ' + error.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Xử lý upload hình ảnh
|
||||
document.querySelectorAll('.btn-upload-image').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const targetInput = this.getAttribute('data-target-input');
|
||||
const imageType = this.getAttribute('data-image-type') || 'level';
|
||||
|
||||
// Lưu thông tin vào hidden inputs
|
||||
document.getElementById('currentImageType').value = imageType;
|
||||
document.getElementById('currentTargetInput').value = targetInput;
|
||||
|
||||
// Kích hoạt input file ẩn
|
||||
document.getElementById('directImageUpload').click();
|
||||
});
|
||||
});
|
||||
|
||||
// Xử lý khi chọn file
|
||||
document.getElementById('directImageUpload').addEventListener('change', async function(e) {
|
||||
if (!this.files || !this.files[0]) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', this.files[0]);
|
||||
const imageType = document.getElementById('currentImageType').value;
|
||||
const targetInput = document.getElementById('currentTargetInput').value;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/admin/upload/image?imageType=${imageType}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.path) {
|
||||
const inputElement = document.getElementById(targetInput);
|
||||
if (inputElement) {
|
||||
inputElement.value = result.path;
|
||||
|
||||
// Cập nhật hoặc tạo mới hình ảnh preview
|
||||
let previewContainer = inputElement.closest('.input-group').parentElement.querySelector('.image-preview');
|
||||
|
||||
// Tìm container image-preview trong cùng hàng nhưng ở cột bên cạnh
|
||||
if (!previewContainer) {
|
||||
previewContainer = inputElement.closest('.row').querySelector('.col-md-6:nth-child(2) .image-preview');
|
||||
}
|
||||
|
||||
// Nếu chưa có container preview thì tạo mới
|
||||
if (!previewContainer) {
|
||||
previewContainer = document.createElement('div');
|
||||
previewContainer.className = 'image-preview mt-2';
|
||||
const rightColumn = inputElement.closest('.row').querySelector('.col-md-6:nth-child(2)');
|
||||
if (rightColumn) {
|
||||
rightColumn.appendChild(previewContainer);
|
||||
} else {
|
||||
// Fallback: tạo tại cột hiện tại nếu không tìm thấy cột bên phải
|
||||
previewContainer = document.createElement('div');
|
||||
previewContainer.className = 'image-preview mt-2';
|
||||
inputElement.closest('.input-group').parentElement.appendChild(previewContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// Tìm hoặc tạo mới thẻ img để preview ảnh
|
||||
let img = previewContainer.querySelector('img');
|
||||
if (!img) {
|
||||
img = document.createElement('img');
|
||||
img.className = 'img-thumbnail';
|
||||
previewContainer.appendChild(img);
|
||||
}
|
||||
|
||||
// Thiết lập style cho ảnh preview dựa vào loại ảnh
|
||||
if (targetInput.toLowerCase().includes('logo')) {
|
||||
img.style.height = '100px';
|
||||
img.style.objectFit = 'contain';
|
||||
img.style.width = '';
|
||||
} else {
|
||||
img.style.height = '200px';
|
||||
img.style.width = '100%';
|
||||
img.style.objectFit = 'cover';
|
||||
}
|
||||
|
||||
img.src = result.path;
|
||||
img.alt = 'Image preview';
|
||||
|
||||
if (window.toastManager) {
|
||||
window.toastManager.success('Tải ảnh thành công');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (window.toastManager) {
|
||||
window.toastManager.error(result.error || 'Error uploading image');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
if (window.toastManager) {
|
||||
window.toastManager.error('Error uploading image');
|
||||
}
|
||||
}
|
||||
|
||||
// Reset input để có thể chọn lại cùng một file nếu cần
|
||||
this.value = '';
|
||||
});
|
||||
});
|
||||
|
||||
// Form submit handler with validation
|
||||
document.getElementById('levelForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
// Create and validate JSON data
|
||||
if (!createJsonFromForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
const requiredFields = ['bannerJson', 'overviewJson', 'requirementsJson'];
|
||||
const missingFields = requiredFields.filter(field => {
|
||||
const value = document.getElementById(field).value;
|
||||
return !value || value === '{}';
|
||||
});
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
if (window.toastManager) {
|
||||
window.toastManager.error(`Missing required data: ${missingFields.join(', ')}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading toast
|
||||
if (window.toastManager) {
|
||||
window.toastManager.info('Saving changes...');
|
||||
}
|
||||
|
||||
this.submit();
|
||||
} catch (error) {
|
||||
console.error('Form submission error:', error);
|
||||
if (window.toastManager) {
|
||||
window.toastManager.error('Error submitting form: ' + error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions for adding/removing items
|
||||
function addParagraph(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
const div = document.createElement('div');
|
||||
div.className = 'mb-3 d-flex gap-2';
|
||||
div.innerHTML = `
|
||||
<textarea class="form-control" rows="3"></textarea>
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeParagraph(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function removeParagraph(button) {
|
||||
button.closest('.mb-3').remove();
|
||||
}
|
||||
|
||||
function addRequirementItem() {
|
||||
const container = document.getElementById('requirementItems');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'mb-3 d-flex gap-2';
|
||||
div.innerHTML = `
|
||||
<input type="text" class="form-control">
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeItem(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function removeItem(button) {
|
||||
button.closest('.mb-3').remove();
|
||||
}
|
||||
|
||||
function addActionButton() {
|
||||
const container = document.getElementById('actionButtonsList');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'mb-3 border p-3';
|
||||
div.innerHTML = `
|
||||
<div class="row g-2">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Text</label>
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Link</label>
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeActionButton(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function removeActionButton(button) {
|
||||
button.closest('.mb-3').remove();
|
||||
}
|
||||
|
||||
function addWhyStudyItem() {
|
||||
const container = document.getElementById('whyStudyItems');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'mb-3 border p-3';
|
||||
div.innerHTML = `
|
||||
<div class="row g-2">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Number</label>
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Title</label>
|
||||
<input type="text" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Text</label>
|
||||
<textarea class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-outline-danger" onclick="removeWhyStudyItem(this)">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function removeWhyStudyItem(button) {
|
||||
button.closest('.mb-3').remove();
|
||||
}
|
||||
|
||||
// (Removed) Helper functions for form/apply/application as Level now references Form only
|
||||
|
||||
// Tận dụng module ImageUploader đã được tải trong layout
|
||||
</script>
|
||||
|
||||
<!-- Modal tùy chỉnh xác nhận xóa level type -->
|
||||
<div id="customModal" class="custom-modal">
|
||||
<div class="custom-modal-content">
|
||||
<div class="custom-modal-header">
|
||||
<h5 class="custom-modal-title">Delete Confirmation</h5>
|
||||
<button type="button" class="custom-modal-close">×</button>
|
||||
</div>
|
||||
<div class="custom-modal-body">
|
||||
<p id="modalMessage">Are you sure you want to delete this level type?</p>
|
||||
</div>
|
||||
<div class="custom-modal-footer">
|
||||
<button type="button" class="btn btn-secondary custom-modal-cancel">Cancel</button>
|
||||
<button type="button" class="btn btn-danger custom-modal-ok">Delete Permanently</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import custom modal CSS -->
|
||||
<link rel="stylesheet" href="/css/custom-modal.css">
|
||||
|
||||
<script>
|
||||
// Custom Modal Functions
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Khởi tạo modal tùy chỉnh
|
||||
CustomModal.init('customModal', {
|
||||
closeOnOutsideClick: true,
|
||||
animationDuration: 300
|
||||
});
|
||||
|
||||
// Mở modal
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.getAttribute('data-custom-modal') === 'open' ||
|
||||
e.target.parentElement.getAttribute('data-custom-modal') === 'open') {
|
||||
|
||||
// Lấy button hoặc icon parent nếu click vào icon
|
||||
const button = e.target.getAttribute('data-custom-modal') === 'open' ?
|
||||
e.target : e.target.parentElement;
|
||||
|
||||
const type = button.getAttribute('data-type') || '<%= currentType %>';
|
||||
|
||||
// Sử dụng CustomModal.confirm thay vì xử lý trực tiếp
|
||||
CustomModal.confirm(
|
||||
`Are you sure you want to delete level type "${type}"? This action cannot be undone. All related data will be permanently deleted.`,
|
||||
function() {
|
||||
// Hành động khi xác nhận
|
||||
window.location.href = `/admin/level/delete/${type}`;
|
||||
},
|
||||
null, // Không cần xử lý khi hủy
|
||||
'Delete Confirmation'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user