253 lines
9.7 KiB
Plaintext
253 lines
9.7 KiB
Plaintext
|
|
@model List<SkyArtShop.Models.HomepageSection>
|
||
|
|
@{
|
||
|
|
ViewData["Title"] = "Homepage Editor";
|
||
|
|
Layout = "_AdminLayout";
|
||
|
|
}
|
||
|
|
|
||
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
|
|
<h2>Homepage Editor</h2>
|
||
|
|
<a href="/admin/homepage/section/create" class="btn btn-primary">
|
||
|
|
<i class="bi bi-plus-circle"></i> Add New Section
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
@if (TempData["SuccessMessage"] != null)
|
||
|
|
{
|
||
|
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||
|
|
@TempData["SuccessMessage"]
|
||
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
|
||
|
|
<!-- Footer Editor -->
|
||
|
|
<div class="card mb-4">
|
||
|
|
<div class="card-header bg-primary text-white">
|
||
|
|
<h5 class="mb-0"><i class="bi bi-footer"></i> Footer Text</h5>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
<form method="post" action="/admin/homepage/footer/update">
|
||
|
|
@Html.AntiForgeryToken()
|
||
|
|
<div class="mb-3">
|
||
|
|
<textarea id="footerText" name="footerText" class="form-control" rows="3">@ViewBag.Settings.FooterText</textarea>
|
||
|
|
</div>
|
||
|
|
<button type="submit" class="btn btn-success">
|
||
|
|
<i class="bi bi-check-circle"></i> Save Footer
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Homepage Sections -->
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-header bg-info text-white">
|
||
|
|
<h5 class="mb-0"><i class="bi bi-layout-text-window-reverse"></i> Homepage Sections</h5>
|
||
|
|
<small>Drag and drop to reorder sections</small>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
@if (Model != null && Model.Any())
|
||
|
|
{
|
||
|
|
<div id="sortable-sections" class="list-group">
|
||
|
|
@foreach (var sect in Model)
|
||
|
|
{
|
||
|
|
<div class="list-group-item section-item" data-id="@sect.Id">
|
||
|
|
<div class="row align-items-center">
|
||
|
|
<div class="col-md-1 text-center drag-handle" style="cursor: grab;">
|
||
|
|
<i class="bi bi-grip-vertical" style="font-size: 1.5rem; color: #6c757d;"></i>
|
||
|
|
</div>
|
||
|
|
<div class="col-md-2">
|
||
|
|
<span class="badge bg-secondary">@sect.SectionType</span>
|
||
|
|
@if (!sect.IsActive)
|
||
|
|
{
|
||
|
|
<span class="badge bg-warning ms-1">Inactive</span>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
<div class="col-md-4">
|
||
|
|
<strong>@sect.Title</strong>
|
||
|
|
@if (!string.IsNullOrEmpty(sect.Subtitle))
|
||
|
|
{
|
||
|
|
<br /><small class="text-muted">@sect.Subtitle</small>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
<div class="col-md-2 text-center">
|
||
|
|
<small class="text-muted">Order: @sect.DisplayOrder</small>
|
||
|
|
</div>
|
||
|
|
<div class="col-md-3 text-end">
|
||
|
|
<div class="d-flex gap-2 justify-content-end">
|
||
|
|
<a href="/admin/homepage/section/@sect.Id" class="btn btn-sm btn-outline-primary" title="Edit Section">
|
||
|
|
<i class="bi bi-pencil"></i> Edit
|
||
|
|
</a>
|
||
|
|
<form method="post" action="/admin/homepage/section/toggle/@sect.Id" class="d-inline">
|
||
|
|
@Html.AntiForgeryToken()
|
||
|
|
<button type="submit" class="btn btn-sm btn-outline-@(sect.IsActive ? "warning" : "success")" title="@(sect.IsActive ? "Deactivate" : "Activate")">
|
||
|
|
<i class="bi bi-@(sect.IsActive ? "eye-slash" : "eye")"></i>
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteSection('@sect.Id')" title="Delete Section">
|
||
|
|
<i class="bi bi-trash"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
<div class="alert alert-info">
|
||
|
|
<i class="bi bi-info-circle"></i> No sections found. Click "Add New Section" to create your first homepage section.
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Preview Button -->
|
||
|
|
<div class="mt-4">
|
||
|
|
<a href="/" target="_blank" class="btn btn-secondary btn-lg">
|
||
|
|
<i class="bi bi-eye"></i> Preview Homepage
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
@section Scripts
|
||
|
|
{
|
||
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||
|
|
<script>
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
// Initialize CKEditor for Footer (if it exists)
|
||
|
|
const footerTextarea = document.querySelector('#footerText');
|
||
|
|
if (footerTextarea && typeof ClassicEditor !== 'undefined') {
|
||
|
|
let footerEditor;
|
||
|
|
ClassicEditor
|
||
|
|
.create(footerTextarea, {
|
||
|
|
toolbar: ['bold', 'italic', 'link']
|
||
|
|
})
|
||
|
|
.then(editor => {
|
||
|
|
footerEditor = editor;
|
||
|
|
const footerForm = footerTextarea.closest('form');
|
||
|
|
if (footerForm) {
|
||
|
|
footerForm.addEventListener('submit', function(e) {
|
||
|
|
footerTextarea.value = footerEditor.getData();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
console.error('CKEditor initialization error:', error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize Sortable for drag & drop
|
||
|
|
const sortableList = document.getElementById('sortable-sections');
|
||
|
|
if (sortableList) {
|
||
|
|
const sortable = Sortable.create(sortableList, {
|
||
|
|
animation: 200,
|
||
|
|
ghostClass: 'sortable-ghost',
|
||
|
|
dragClass: 'sortable-drag',
|
||
|
|
handle: '.drag-handle',
|
||
|
|
draggable: '.section-item',
|
||
|
|
onStart: function(evt) {
|
||
|
|
evt.item.style.cursor = 'grabbing';
|
||
|
|
},
|
||
|
|
onEnd: function (evt) {
|
||
|
|
evt.item.style.cursor = '';
|
||
|
|
const sectionIds = Array.from(sortableList.children).map(item => item.getAttribute('data-id'));
|
||
|
|
|
||
|
|
fetch('/admin/homepage/section/reorder', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
|
||
|
|
},
|
||
|
|
body: JSON.stringify(sectionIds)
|
||
|
|
})
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(data => {
|
||
|
|
if (data.success) {
|
||
|
|
// Update display order numbers
|
||
|
|
sortableList.querySelectorAll('.section-item').forEach((item, index) => {
|
||
|
|
item.querySelector('.col-md-2.text-center small').textContent = 'Order: ' + index;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
console.error('Error updating section order:', error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
console.log('sortable-sections element not found');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
function deleteSection(id) {
|
||
|
|
if (confirm('Are you sure you want to delete this section?')) {
|
||
|
|
const form = document.createElement('form');
|
||
|
|
form.method = 'POST';
|
||
|
|
form.action = '/admin/homepage/section/delete/' + id;
|
||
|
|
|
||
|
|
const token = document.querySelector('input[name="__RequestVerificationToken"]').cloneNode();
|
||
|
|
form.appendChild(token);
|
||
|
|
|
||
|
|
document.body.appendChild(form);
|
||
|
|
form.submit();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
<style>
|
||
|
|
.section-item {
|
||
|
|
transition: all 0.3s ease;
|
||
|
|
margin-bottom: 12px;
|
||
|
|
border-left: 4px solid #6c757d;
|
||
|
|
background: white;
|
||
|
|
padding: 15px;
|
||
|
|
border-radius: 6px;
|
||
|
|
}
|
||
|
|
.section-item:hover {
|
||
|
|
background-color: #f8f9fa;
|
||
|
|
border-left-color: #0d6efd;
|
||
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
|
|
}
|
||
|
|
.drag-handle {
|
||
|
|
transition: all 0.2s ease;
|
||
|
|
cursor: grab;
|
||
|
|
user-select: none;
|
||
|
|
-webkit-user-select: none;
|
||
|
|
-moz-user-select: none;
|
||
|
|
-ms-user-select: none;
|
||
|
|
}
|
||
|
|
.drag-handle:hover {
|
||
|
|
transform: scale(1.1);
|
||
|
|
color: #0d6efd !important;
|
||
|
|
cursor: grab;
|
||
|
|
}
|
||
|
|
.drag-handle:active {
|
||
|
|
cursor: grabbing !important;
|
||
|
|
}
|
||
|
|
#sortable-sections {
|
||
|
|
list-style: none;
|
||
|
|
padding: 0;
|
||
|
|
}
|
||
|
|
.sortable-ghost {
|
||
|
|
opacity: 0.5;
|
||
|
|
background: #e3f2fd !important;
|
||
|
|
border: 2px dashed #0d6efd !important;
|
||
|
|
}
|
||
|
|
.sortable-drag {
|
||
|
|
opacity: 0.8;
|
||
|
|
cursor: grabbing !important;
|
||
|
|
transform: rotate(2deg);
|
||
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.3) !important;
|
||
|
|
}
|
||
|
|
.sortable-fallback {
|
||
|
|
opacity: 0.8;
|
||
|
|
background: white !important;
|
||
|
|
box-shadow: 0 5px 20px rgba(0,0,0,0.3) !important;
|
||
|
|
}
|
||
|
|
.btn-group .btn, .d-flex .btn {
|
||
|
|
min-width: 75px;
|
||
|
|
}
|
||
|
|
.list-group-item {
|
||
|
|
border: 1px solid #dee2e6;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
}
|