Files
cms.uldp.edu.vn/views/admin/safety/index.ejs
r2xrzh9q2z-lab d1b931d547 first commit
2026-02-02 11:07:09 +07:00

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>