forked from UKSOURCE/cms.hailearning.edu.vn
1103 lines
43 KiB
Plaintext
1103 lines
43 KiB
Plaintext
<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)"><%= title %></h1>
|
|
<p class="text-muted mb-0">Edit content displayed on Safety page</p>
|
|
</div>
|
|
<div>
|
|
<a
|
|
href="<%= frontendUrl %>/safety/"
|
|
class="btn btn-outline-primary"
|
|
target="_blank"
|
|
>
|
|
<i class="fas fa-external-link-alt me-2"></i>View Safety Page
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<form
|
|
method="POST"
|
|
class="content-with-fixed-buttons"
|
|
id="safetyForm"
|
|
action="/admin/safety/update"
|
|
>
|
|
<!-- Hidden inputs for JSON data -->
|
|
<input type="hidden" name="hero" id="heroJson">
|
|
<input type="hidden" name="approach" id="approachJson">
|
|
<input type="hidden" name="philosophy" id="philosophyJson">
|
|
<input type="hidden" name="security" id="securityJson">
|
|
|
|
<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" role="presentation">
|
|
<button
|
|
class="nav-link active"
|
|
id="tab-hero-tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#tab-hero"
|
|
type="button"
|
|
role="tab"
|
|
>
|
|
<i class="fas fa-home me-2"></i>
|
|
Hero
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button
|
|
class="nav-link"
|
|
id="tab-approach-tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#tab-approach"
|
|
type="button"
|
|
role="tab"
|
|
>
|
|
<i class="fas fa-compass me-2"></i>
|
|
Approach
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button
|
|
class="nav-link"
|
|
id="tab-philosophy-tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#tab-philosophy"
|
|
type="button"
|
|
role="tab"
|
|
>
|
|
<i class="fas fa-book-open me-2"></i>
|
|
Philosophy
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button
|
|
class="nav-link"
|
|
id="tab-security-tab"
|
|
data-bs-toggle="tab"
|
|
data-bs-target="#tab-security"
|
|
type="button"
|
|
role="tab"
|
|
>
|
|
<i class="fas fa-shield-alt me-2"></i>
|
|
Security
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="tab-content">
|
|
<!-- HERO TAB -->
|
|
<div
|
|
class="tab-pane fade show active"
|
|
id="tab-hero"
|
|
role="tabpanel"
|
|
>
|
|
<label class="form-label fw-bold">Banner Image</label>
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="input-group">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="hero.banner"
|
|
id="heroBannerImage"
|
|
value="<%= data.hero.banner %>"
|
|
onchange="updateImagePreview(this, 'heroBannerPreview')"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="heroBannerImage"
|
|
data-image-type="safety"
|
|
data-preview-id="heroBannerPreview"
|
|
>
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<% if (data.hero.banner) { %>
|
|
<img src="<%= data.hero.banner %>" class="image-preview" id="heroBannerPreview" alt="Banner Preview">
|
|
<% } else { %>
|
|
<img src="" class="image-preview" id="heroBannerPreview" alt="Banner Preview" style="display: none;">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="hero.title"
|
|
value="<%= data.hero.title %>"
|
|
/>
|
|
</div>
|
|
<!-- APPROACH TAB -->
|
|
<div class="tab-pane fade" id="tab-approach" role="tabpanel">
|
|
<label class="form-label fw-bold">Badge</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="approach.badge"
|
|
value="<%= data.approach.badge %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="approach.title"
|
|
value="<%= data.approach.title %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Description</label>
|
|
<textarea
|
|
class="form-control mb-3"
|
|
rows="4"
|
|
name="approach.description"
|
|
>
|
|
<%= data.approach.description %></textarea
|
|
>
|
|
|
|
|
|
<h5 class="fw-bold highlight-text mt-5">Images</h5>
|
|
|
|
<label class="form-label fw-bold">Image 1 URL</label>
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="input-group">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="approach.imgs.img1"
|
|
id="approachImg1"
|
|
value="<%= data.approach.imgs.img1 %>"
|
|
onchange="updateImagePreview(this, 'approachImg1Preview')"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="approachImg1"
|
|
data-image-type="safety"
|
|
data-preview-id="approachImg1Preview"
|
|
>
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<% if (data.approach.imgs.img1) { %>
|
|
<img src="<%= data.approach.imgs.img1 %>" class="image-preview" id="approachImg1Preview" alt="Image 1 Preview">
|
|
<% } else { %>
|
|
<img src="" class="image-preview" id="approachImg1Preview" alt="Image 1 Preview" style="display: none;">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<label class="form-label fw-bold">Image 2 URL</label>
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="input-group">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="approach.imgs.img2"
|
|
id="approachImg2"
|
|
value="<%= data.approach.imgs.img2 %>"
|
|
onchange="updateImagePreview(this, 'approachImg2Preview')"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="approachImg2"
|
|
data-image-type="safety"
|
|
data-preview-id="approachImg2Preview"
|
|
>
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<% if (data.approach.imgs.img2) { %>
|
|
<img src="<%= data.approach.imgs.img2 %>" class="image-preview" id="approachImg2Preview" alt="Image 2 Preview">
|
|
<% } else { %>
|
|
<img src="" class="image-preview" id="approachImg2Preview" alt="Image 2 Preview" style="display: none;">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<h5 class="fw-bold mt-5 highlight-text">Statistics</h5>
|
|
<label class="form-label fw-bold">Count</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="approach.stats.count"
|
|
value="<%= data.approach.stats.count %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Label</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="approach.stats.label"
|
|
value="<%= data.approach.stats.label %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Avatars</label>
|
|
<div class="row mb-3">
|
|
<% for (let i = 0; i < 3; i++) { %>
|
|
<div class="col-md-4 mb-3">
|
|
<div class="avatar-preview-container">
|
|
<div class="input-group mb-2">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="approach.stats.avatars.<%= i %>"
|
|
id="approachAvatar<%= i %>"
|
|
value="<%= data.approach.stats.avatars[i] || '' %>"
|
|
onchange="updateAvatarPreview(this, <%= i %>)"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="approachAvatar<%= i %>"
|
|
data-image-type="safety"
|
|
>
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
<% if (data.approach.stats.avatars[i]) { %>
|
|
<img src="<%= data.approach.stats.avatars[i] %>" class="approach-avatar-preview" id="avatarPreview<%= i %>" alt="Avatar <%= i + 1 %>">
|
|
<% } else { %>
|
|
<img src="" class="approach-avatar-preview" id="avatarPreview<%= i %>" alt="Avatar <%= i + 1 %>" style="display: none;">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
|
|
<h5 class="fw-bold mt-5 highlight-text">Features</h5>
|
|
<% data.approach.features.forEach((f, i) => { %>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-2"
|
|
name="approach.features.<%= i %>.text"
|
|
value="<%= f.text %>"
|
|
/>
|
|
<% }) %>
|
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="fw-bold mt-5 highlight-text mb-0">Cards</h5>
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="addApproachCard()">
|
|
<i class="fas fa-plus me-1"></i>Add Card
|
|
</button>
|
|
</div>
|
|
<div id="approachCardsContainer">
|
|
<% data.approach.cards.forEach((c, i) => { %>
|
|
<div class="border rounded p-3 mb-5 bg-light approach-card" data-index="<%= i %>">
|
|
<div class="d-flex justify-content-end mb-2">
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="deleteApproachCard(this)">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
<div class="row">
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-2"
|
|
name="approach.cards.<%= i %>.title"
|
|
value="<%= c.title %>"
|
|
/>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-bold">Content</label>
|
|
<textarea
|
|
class="form-control"
|
|
rows="3"
|
|
name="approach.cards.<%= i %>.content"
|
|
><%= c.content %></textarea
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<% }) %>
|
|
</div>
|
|
</div>
|
|
<!-- PHILOSOPHY TAB -->
|
|
<div class="tab-pane fade" id="tab-philosophy" role="tabpanel">
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="philosophy.title"
|
|
value="<%= data.philosophy.title %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Subtitle</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="philosophy.subtitle"
|
|
value="<%= data.philosophy.subtitle %>"
|
|
/>
|
|
|
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="fw-bold mt-5 highlight-text mb-0">Cards</h5>
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="addPhilosophyCard()">
|
|
<i class="fas fa-plus me-1"></i>Add Card
|
|
</button>
|
|
</div>
|
|
<div id="philosophyCardsContainer">
|
|
<% data.philosophy.cards.forEach((c, i) => { %>
|
|
<div class="border rounded p-3 mb-5 bg-light philosophy-card" data-index="<%= i %>">
|
|
<div class="d-flex justify-content-end mb-2">
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="deletePhilosophyCard(this)">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-2"
|
|
name="philosophy.cards.<%= i %>.title"
|
|
value="<%= c.title %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Content</label>
|
|
<textarea
|
|
class="form-control mb-2"
|
|
rows="3"
|
|
name="philosophy.cards.<%= i %>.content"
|
|
>
|
|
<%= c.content %></textarea
|
|
>
|
|
|
|
<label class="form-label fw-bold">Author Avatar</label>
|
|
<div class="row mb-2">
|
|
<div class="col-md-8">
|
|
<div class="input-group">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="philosophy.cards.<%= i %>.author.avt"
|
|
id="philosophyAvatar<%= i %>"
|
|
value="<%= c.author.avt %>"
|
|
onchange="updateImagePreview(this, 'philosophyAvatarPreview<%= i %>')"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="philosophyAvatar<%= i %>"
|
|
data-image-type="safety"
|
|
data-preview-id="philosophyAvatarPreview<%= i %>"
|
|
>
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 text-left mb-4">
|
|
<% if (c.author.avt) { %>
|
|
<img src="<%= c.author.avt %>" class="author-avatar-preview" id="philosophyAvatarPreview<%= i %>" alt="Author Avatar">
|
|
<% } else { %>
|
|
<img src="" class="author-avatar-preview" id="philosophyAvatarPreview<%= i %>" alt="Author Avatar" style="display: none;">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Name</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="philosophy.cards.<%= i %>.author.name"
|
|
value="<%= c.author.name %>"
|
|
/>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Role</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="philosophy.cards.<%= i %>.author.role"
|
|
value="<%= c.author.role %>"
|
|
/>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Rating</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="philosophy.cards.<%= i %>.author.rating"
|
|
value="<%= c.author.rating %>"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% }) %>
|
|
</div>
|
|
</div>
|
|
<!-- SECURITY TAB -->
|
|
<div class="tab-pane fade" id="tab-security" role="tabpanel">
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="security.title"
|
|
value="<%= data.security.title %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Subtitle</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-3"
|
|
name="security.subtitle"
|
|
value="<%= data.security.subtitle %>"
|
|
/>
|
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="fw-bold mt-5 highlight-text mb-0">Cards</h5>
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="addSecurityCard()">
|
|
<i class="fas fa-plus me-1"></i>Add Card
|
|
</button>
|
|
</div>
|
|
<div id="securityCardsContainer">
|
|
<% data.security.cards.forEach((c, i) => { %>
|
|
<div class="border rounded p-3 mb-5 bg-light security-card" data-index="<%= i %>">
|
|
<div class="d-flex justify-content-end mb-2">
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="deleteSecurityCard(this)">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input
|
|
type="text"
|
|
class="form-control mb-2"
|
|
name="security.cards.<%= i %>.title"
|
|
value="<%= c.title %>"
|
|
/>
|
|
|
|
<label class="form-label fw-bold">Content</label>
|
|
<textarea
|
|
class="form-control mb-2"
|
|
rows="3"
|
|
name="security.cards.<%= i %>.content"
|
|
>
|
|
<%= c.content %></textarea
|
|
>
|
|
|
|
<label class="form-label fw-bold">Author Avatar</label>
|
|
<div class="row mb-2">
|
|
<div class="col-md-8">
|
|
<div class="input-group">
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="security.cards.<%= i %>.author.avt"
|
|
id="securityAvatar<%= i %>"
|
|
value="<%= c.author.avt %>"
|
|
onchange="updateImagePreview(this, 'securityAvatarPreview<%= i %>')"
|
|
/>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="securityAvatar<%= i %>"
|
|
data-image-type="safety"
|
|
data-preview-id="securityAvatarPreview<%= i %>"
|
|
>
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 text-left mb-4">
|
|
<% if (c.author.avt) { %>
|
|
<img src="<%= c.author.avt %>" class="author-avatar-preview" id="securityAvatarPreview<%= i %>" alt="Author Avatar">
|
|
<% } else { %>
|
|
<img src="" class="author-avatar-preview" id="securityAvatarPreview<%= i %>" alt="Author Avatar" style="display: none;">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Name</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="security.cards.<%= i %>.author.name"
|
|
value="<%= c.author.name %>"
|
|
/>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Role</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="security.cards.<%= i %>.author.role"
|
|
value="<%= c.author.role %>"
|
|
/>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Rating</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
name="security.cards.<%= i %>.author.rating"
|
|
value="<%= c.author.rating %>"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<% }) %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fixed Bottom Buttons -->
|
|
<div class="fixed-bottom-buttons">
|
|
<button type="reset" class="btn btn-secondary" onclick="resetForm()">
|
|
<i class="fas fa-undo me-2"></i>Reset
|
|
</button>
|
|
<button type="submit" class="btn btn-primary" id="submitBtn">
|
|
<i class="fas fa-save me-2"></i>Save Changes
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.approach-avatar-preview {
|
|
width: 100px;
|
|
height: 100px;
|
|
object-fit: cover;
|
|
border: 2px solid #e0e0e0;
|
|
background: #f5f5f5;
|
|
display: block;
|
|
margin-top: 8px;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.avatar-preview-container {
|
|
min-height: 76px;
|
|
}
|
|
|
|
.image-preview {
|
|
max-width: 100%;
|
|
max-height: 200px;
|
|
object-fit: contain;
|
|
border: 2px solid #e0e0e0;
|
|
background: #f5f5f5;
|
|
display: block;
|
|
border-radius: 8px;
|
|
padding: 4px;
|
|
}
|
|
|
|
.author-avatar-preview {
|
|
width: 60px;
|
|
height: 60px;
|
|
object-fit: cover;
|
|
|
|
border: 2px solid #e0e0e0;
|
|
background: #f5f5f5;
|
|
display: inline-block;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
let originalFormData = null;
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
originalFormData = <%- JSON.stringify(data) %>;
|
|
|
|
// Initialize form submission handler
|
|
const form = document.getElementById('safetyForm');
|
|
form.addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
const submitBtn = document.getElementById('submitBtn');
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
|
|
|
|
try {
|
|
updateJsonData();
|
|
this.submit();
|
|
} catch (error) {
|
|
console.error('Error updating data:', error);
|
|
alert('Failed to process form data. Please try again.');
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = '<i class="fas fa-save me-2"></i>Save Changes';
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll(".btn-upload-image").forEach((button) => {
|
|
button.addEventListener("click", function () {
|
|
const targetInput = this.dataset.targetInput;
|
|
const imageType = this.dataset.imageType;
|
|
openImageUploader(targetInput, imageType);
|
|
});
|
|
});
|
|
});
|
|
|
|
function updateJsonData() {
|
|
try {
|
|
// Hero data
|
|
const heroData = {
|
|
title: document.querySelector('[name="hero.title"]')?.value || '',
|
|
banner: document.querySelector('[name="hero.banner"]')?.value || ''
|
|
};
|
|
document.getElementById('heroJson').value = JSON.stringify(heroData);
|
|
|
|
// Approach data
|
|
const approachData = {
|
|
badge: document.querySelector('[name="approach.badge"]')?.value || '',
|
|
title: document.querySelector('[name="approach.title"]')?.value || '',
|
|
description: document.querySelector('[name="approach.description"]')?.value || '',
|
|
imgs: {
|
|
img1: document.querySelector('[name="approach.imgs.img1"]')?.value || '',
|
|
img2: document.querySelector('[name="approach.imgs.img2"]')?.value || ''
|
|
},
|
|
stats: {
|
|
count: document.querySelector('[name="approach.stats.count"]')?.value || '',
|
|
label: document.querySelector('[name="approach.stats.label"]')?.value || '',
|
|
avatars: [
|
|
document.querySelector('[name="approach.stats.avatars.0"]')?.value || '',
|
|
document.querySelector('[name="approach.stats.avatars.1"]')?.value || '',
|
|
document.querySelector('[name="approach.stats.avatars.2"]')?.value || ''
|
|
]
|
|
},
|
|
features: Array.from(document.querySelectorAll('[name^="approach.features."]')).map(input => ({
|
|
text: input.value || ''
|
|
})),
|
|
cards: []
|
|
};
|
|
|
|
// Collect approach cards
|
|
const approachCardTitles = document.querySelectorAll('[name^="approach.cards."][name$=".title"]');
|
|
approachCardTitles.forEach((titleInput, i) => {
|
|
const contentInput = document.querySelector(`[name="approach.cards.${i}.content"]`);
|
|
if (titleInput && contentInput) {
|
|
approachData.cards.push({
|
|
title: titleInput.value || '',
|
|
content: contentInput.value || ''
|
|
});
|
|
}
|
|
});
|
|
document.getElementById('approachJson').value = JSON.stringify(approachData);
|
|
|
|
// Philosophy data
|
|
const philosophyData = {
|
|
title: document.querySelector('[name="philosophy.title"]')?.value || '',
|
|
subtitle: document.querySelector('[name="philosophy.subtitle"]')?.value || '',
|
|
cards: []
|
|
};
|
|
|
|
// Collect philosophy cards
|
|
const philosophyCardTitles = document.querySelectorAll('[name^="philosophy.cards."][name$=".title"]');
|
|
philosophyCardTitles.forEach((titleInput, i) => {
|
|
const contentInput = document.querySelector(`[name="philosophy.cards.${i}.content"]`);
|
|
const avtInput = document.querySelector(`[name="philosophy.cards.${i}.author.avt"]`);
|
|
const nameInput = document.querySelector(`[name="philosophy.cards.${i}.author.name"]`);
|
|
const roleInput = document.querySelector(`[name="philosophy.cards.${i}.author.role"]`);
|
|
const ratingInput = document.querySelector(`[name="philosophy.cards.${i}.author.rating"]`);
|
|
|
|
philosophyData.cards.push({
|
|
title: titleInput?.value || '',
|
|
content: contentInput?.value || '',
|
|
author: {
|
|
avt: avtInput?.value || '',
|
|
name: nameInput?.value || '',
|
|
role: roleInput?.value || '',
|
|
rating: ratingInput?.value || ''
|
|
}
|
|
});
|
|
});
|
|
document.getElementById('philosophyJson').value = JSON.stringify(philosophyData);
|
|
|
|
// Security data
|
|
const securityData = {
|
|
title: document.querySelector('[name="security.title"]')?.value || '',
|
|
subtitle: document.querySelector('[name="security.subtitle"]')?.value || '',
|
|
cards: []
|
|
};
|
|
|
|
// Collect security cards
|
|
const securityCardTitles = document.querySelectorAll('[name^="security.cards."][name$=".title"]');
|
|
securityCardTitles.forEach((titleInput, i) => {
|
|
const contentInput = document.querySelector(`[name="security.cards.${i}.content"]`);
|
|
const avtInput = document.querySelector(`[name="security.cards.${i}.author.avt"]`);
|
|
const nameInput = document.querySelector(`[name="security.cards.${i}.author.name"]`);
|
|
const roleInput = document.querySelector(`[name="security.cards.${i}.author.role"]`);
|
|
const ratingInput = document.querySelector(`[name="security.cards.${i}.author.rating"]`);
|
|
|
|
securityData.cards.push({
|
|
title: titleInput?.value || '',
|
|
content: contentInput?.value || '',
|
|
author: {
|
|
avt: avtInput?.value || '',
|
|
name: nameInput?.value || '',
|
|
role: roleInput?.value || '',
|
|
rating: ratingInput?.value || ''
|
|
}
|
|
});
|
|
});
|
|
document.getElementById('securityJson').value = JSON.stringify(securityData);
|
|
|
|
} catch (error) {
|
|
console.error('Error updating JSON data:', error);
|
|
throw new Error('Failed to process form data');
|
|
}
|
|
}
|
|
|
|
function resetForm() {
|
|
if (confirm('Are you sure you want to reset all changes?')) {
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function updateAvatarPreview(input, index) {
|
|
const preview = document.getElementById('avatarPreview' + index);
|
|
if (preview && input.value) {
|
|
preview.src = input.value;
|
|
preview.style.display = 'block';
|
|
} else if (preview) {
|
|
preview.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function updateImagePreview(input, previewId) {
|
|
const preview = document.getElementById(previewId);
|
|
if (preview && input.value) {
|
|
preview.src = input.value;
|
|
preview.style.display = 'block';
|
|
} else if (preview) {
|
|
preview.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Generic Card Management Functions
|
|
const cardTemplates = {
|
|
approach: (index) => `
|
|
<div class="border rounded p-3 mb-5 bg-light approach-card" data-index="${index}">
|
|
<div class="d-flex justify-content-end mb-2">
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="deleteCard('approach', this)">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input type="text" class="form-control mb-2" name="approach.cards.${index}.title" value="" />
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-bold">Content</label>
|
|
<textarea class="form-control" rows="3" name="approach.cards.${index}.content"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`,
|
|
philosophy: (index) => `
|
|
<div class="border rounded p-3 mb-5 bg-light philosophy-card" data-index="${index}">
|
|
<div class="d-flex justify-content-end mb-2">
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="deleteCard('philosophy', this)">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input type="text" class="form-control mb-2" name="philosophy.cards.${index}.title" value="" />
|
|
|
|
<label class="form-label fw-bold">Content</label>
|
|
<textarea class="form-control mb-2" rows="3" name="philosophy.cards.${index}.content"></textarea>
|
|
|
|
<label class="form-label fw-bold">Author Avatar</label>
|
|
<div class="row mb-2">
|
|
<div class="col-md-8">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" name="philosophy.cards.${index}.author.avt" id="philosophyAvatar${index}" value="" onchange="updateImagePreview(this, 'philosophyAvatarPreview${index}')" />
|
|
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="philosophyAvatar${index}" data-image-type="safety" data-preview-id="philosophyAvatarPreview${index}">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 text-left mb-4">
|
|
<img src="" class="author-avatar-preview" id="philosophyAvatarPreview${index}" alt="Author Avatar" style="display: none;">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Name</label>
|
|
<input type="text" class="form-control" name="philosophy.cards.${index}.author.name" value="" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Role</label>
|
|
<input type="text" class="form-control" name="philosophy.cards.${index}.author.role" value="" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Rating</label>
|
|
<input type="text" class="form-control" name="philosophy.cards.${index}.author.rating" value="" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`,
|
|
security: (index) => `
|
|
<div class="border rounded p-3 mb-5 bg-light security-card" data-index="${index}">
|
|
<div class="d-flex justify-content-end mb-2">
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="deleteCard('security', this)">
|
|
<i class="fas fa-trash me-1"></i>Delete
|
|
</button>
|
|
</div>
|
|
<label class="form-label fw-bold">Title</label>
|
|
<input type="text" class="form-control mb-2" name="security.cards.${index}.title" value="" />
|
|
|
|
<label class="form-label fw-bold">Content</label>
|
|
<textarea class="form-control mb-2" rows="3" name="security.cards.${index}.content"></textarea>
|
|
|
|
<label class="form-label fw-bold">Author Avatar</label>
|
|
<div class="row mb-2">
|
|
<div class="col-md-8">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" name="security.cards.${index}.author.avt" id="securityAvatar${index}" value="" onchange="updateImagePreview(this, 'securityAvatarPreview${index}')" />
|
|
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="securityAvatar${index}" data-image-type="safety" data-preview-id="securityAvatarPreview${index}">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 text-left mb-4">
|
|
<img src="" class="author-avatar-preview" id="securityAvatarPreview${index}" alt="Author Avatar" style="display: none;">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Name</label>
|
|
<input type="text" class="form-control" name="security.cards.${index}.author.name" value="" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Author Role</label>
|
|
<input type="text" class="form-control" name="security.cards.${index}.author.role" value="" />
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">Rating</label>
|
|
<input type="text" class="form-control" name="security.cards.${index}.author.rating" value="" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
};
|
|
|
|
function addCard(type) {
|
|
const container = document.getElementById(`${type}CardsContainer`);
|
|
const cards = container.querySelectorAll(`.${type}-card`);
|
|
const newIndex = cards.length;
|
|
|
|
container.insertAdjacentHTML('beforeend', cardTemplates[type](newIndex));
|
|
|
|
// Re-attach upload button event listeners for new card
|
|
const newCard = container.lastElementChild;
|
|
newCard.querySelectorAll('.btn-upload-image').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
openImageUploader(this.dataset.targetInput, this.dataset.imageType);
|
|
});
|
|
});
|
|
}
|
|
|
|
function deleteCard(type, btn) {
|
|
if (confirm('Are you sure you want to delete this card?')) {
|
|
btn.closest(`.${type}-card`).remove();
|
|
reindexCards(type);
|
|
}
|
|
}
|
|
|
|
function reindexCards(type) {
|
|
const cards = document.querySelectorAll(`.${type}-card`);
|
|
cards.forEach((card, index) => {
|
|
card.setAttribute('data-index', index);
|
|
const prefix = `${type}.cards.`;
|
|
const avatarPrefix = `${type}Avatar`;
|
|
|
|
card.querySelectorAll('input, textarea').forEach(input => {
|
|
const name = input.getAttribute('name');
|
|
const id = input.getAttribute('id');
|
|
|
|
if (name) {
|
|
input.setAttribute('name', name.replace(new RegExp(`${prefix}\\d+`), `${prefix}${index}`));
|
|
}
|
|
if (id && id.includes(avatarPrefix)) {
|
|
const newId = `${avatarPrefix}${index}`;
|
|
input.setAttribute('id', newId);
|
|
const btn = card.querySelector(`[data-target-input="${id}"]`);
|
|
if (btn) {
|
|
btn.setAttribute('data-target-input', newId);
|
|
btn.setAttribute('data-preview-id', `${avatarPrefix}Preview${index}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
card.querySelectorAll('img').forEach(img => {
|
|
const id = img.getAttribute('id');
|
|
if (id && id.includes(`${avatarPrefix}Preview`)) {
|
|
img.setAttribute('id', `${avatarPrefix}Preview${index}`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Wrapper functions for onclick handlers
|
|
function addApproachCard() { addCard('approach'); }
|
|
function addPhilosophyCard() { addCard('philosophy'); }
|
|
function addSecurityCard() { addCard('security'); }
|
|
function deleteApproachCard(btn) { deleteCard('approach', btn); }
|
|
function deletePhilosophyCard(btn) { deleteCard('philosophy', btn); }
|
|
function deleteSecurityCard(btn) { deleteCard('security', btn); }
|
|
function reindexApproachCards() { reindexCards('approach'); }
|
|
function reindexPhilosophyCards() { reindexCards('philosophy'); }
|
|
function reindexSecurityCards() { reindexCards('security'); }
|
|
|
|
function openImageUploader(targetInput, imageType) {
|
|
const fileInput = document.createElement("input");
|
|
fileInput.type = "file";
|
|
fileInput.accept = "image/*";
|
|
fileInput.style.display = "none";
|
|
document.body.appendChild(fileInput);
|
|
|
|
fileInput.onchange = async function (e) {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append("image", file);
|
|
|
|
const uploadBtn = document.querySelector(
|
|
`[data-target-input="${targetInput}"]`
|
|
);
|
|
const originalBtnHtml = uploadBtn.innerHTML;
|
|
uploadBtn.disabled = true;
|
|
uploadBtn.innerHTML =
|
|
'<i class="fas fa-spinner fa-spin me-1"></i>Uploading...';
|
|
|
|
const response = await fetch(
|
|
`/admin/upload/image?imageType=${imageType}`,
|
|
{
|
|
method: "POST",
|
|
body: formData,
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Upload failed");
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || "Upload failed");
|
|
}
|
|
|
|
const input =
|
|
document.getElementById(targetInput) ||
|
|
document.querySelector(`[name="${targetInput}"]`);
|
|
if (!input) {
|
|
throw new Error("Target input not found");
|
|
}
|
|
|
|
const previewUrl =
|
|
result.path &&
|
|
(result.path.startsWith("http://") ||
|
|
result.path.startsWith("https://"))
|
|
? result.path
|
|
: window.location.origin + result.path;
|
|
|
|
input.value = result.path;
|
|
|
|
// Update preview based on button's data-preview-id attribute
|
|
const previewId = uploadBtn.getAttribute('data-preview-id');
|
|
if (previewId) {
|
|
updateImagePreview(input, previewId);
|
|
}
|
|
|
|
// Update avatar preview if it's an approach avatar
|
|
if (targetInput.startsWith('approachAvatar')) {
|
|
const avatarIndex = targetInput.replace('approachAvatar', '');
|
|
updateAvatarPreview(input, avatarIndex);
|
|
}
|
|
|
|
showToast("Success", "Image uploaded successfully", "success");
|
|
|
|
uploadBtn.disabled = false;
|
|
uploadBtn.innerHTML = originalBtnHtml;
|
|
} catch (error) {
|
|
console.error("Upload error:", error);
|
|
showToast("Error", "Failed to upload image: " + error.message, "error");
|
|
|
|
const uploadBtn = document.querySelector(
|
|
`[data-target-input="${targetInput}"]`
|
|
);
|
|
if (uploadBtn) {
|
|
uploadBtn.disabled = false;
|
|
uploadBtn.innerHTML = uploadBtn.innerHTML.replace(
|
|
"Uploading...",
|
|
"Upload"
|
|
);
|
|
}
|
|
} finally {
|
|
document.body.removeChild(fileInput);
|
|
}
|
|
};
|
|
|
|
fileInput.click();
|
|
}
|
|
|
|
function showToast(title, message, type = "info") {
|
|
const toast = document.createElement("div");
|
|
toast.className = `toast align-items-center text-white bg-${
|
|
type === "error" ? "danger" : type
|
|
} border-0`;
|
|
toast.setAttribute("role", "alert");
|
|
toast.setAttribute("aria-live", "assertive");
|
|
toast.setAttribute("aria-atomic", "true");
|
|
|
|
toast.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
<strong>${title}:</strong> ${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
`;
|
|
|
|
let container = document.querySelector(".toast-container");
|
|
if (!container) {
|
|
container = document.createElement("div");
|
|
container.className = "toast-container position-fixed top-0 end-0 p-3";
|
|
document.body.appendChild(container);
|
|
}
|
|
container.appendChild(toast);
|
|
|
|
const bsToast = new bootstrap.Toast(toast, {
|
|
animation: true,
|
|
autohide: true,
|
|
delay: 3000,
|
|
});
|
|
bsToast.show();
|
|
|
|
toast.addEventListener("hidden.bs.toast", () => {
|
|
toast.remove();
|
|
});
|
|
}
|
|
</script>
|