Files
tomsjavajive/admin/splashes.php
T
2026-05-22 12:52:44 +00:00

411 lines
21 KiB
PHP

<?php
ob_start();
$pageTitle = 'Splash Box';
$currentPage = 'splashes';
require_once __DIR__ . '/includes/header.php';
/* ── Actions ─────────────────────────────────────── */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$splashId = $_POST['splash_id'] ?? '';
if ($action === 'create') {
$title = trim($_POST['title'] ?? '');
if ($title) {
db()->insert('homepage_splashes', [
'splash_id' => generateId('spl_'),
'icon' => trim($_POST['icon'] ?? 'fas fa-star'),
'image_url' => trim($_POST['image_url'] ?? '') ?: null,
'title' => $title,
'description' => trim($_POST['description'] ?? '') ?: null,
'sort_order' => intval($_POST['sort_order'] ?? 0),
'is_active' => 1,
]);
setFlash('success', 'Splash block created');
}
}
if ($action === 'update' && $splashId) {
$title = trim($_POST['title'] ?? '');
if ($title) {
db()->update('homepage_splashes', [
'icon' => trim($_POST['icon'] ?? 'fas fa-star'),
'image_url' => trim($_POST['image_url'] ?? '') ?: null,
'title' => $title,
'description' => trim($_POST['description'] ?? '') ?: null,
'sort_order' => intval($_POST['sort_order'] ?? 0),
'is_active' => isset($_POST['is_active']) ? 1 : 0,
], 'splash_id = :id', ['id' => $splashId]);
setFlash('success', 'Splash block updated');
}
}
if ($action === 'delete' && $splashId) {
db()->delete('homepage_splashes', 'splash_id = :id', ['id' => $splashId]);
setFlash('success', 'Splash block deleted');
}
if ($action === 'reorder') {
$ids = json_decode($_POST['order'] ?? '[]', true);
foreach ($ids as $pos => $sid) {
db()->update('homepage_splashes', ['sort_order' => $pos + 1],
'splash_id = :id', ['id' => $sid]);
}
echo json_encode(['ok' => true]); exit;
}
header('Location: /admin/splashes.php'); exit;
}
$splashes = db()->fetchAll(
"SELECT * FROM homepage_splashes ORDER BY sort_order ASC, id ASC"
);
$iconOptions = [
'fas fa-leaf' => 'Leaf',
'fas fa-fire' => 'Fire',
'fas fa-truck' => 'Truck',
'fas fa-heart' => 'Heart',
'fas fa-star' => 'Star',
'fas fa-coffee' => 'Coffee',
'fas fa-mug-hot' => 'Mug Hot',
'fas fa-seedling' => 'Seedling',
'fas fa-shield-alt' => 'Shield',
'fas fa-check-circle' => 'Check',
'fas fa-gift' => 'Gift',
'fas fa-globe' => 'Globe',
'fas fa-award' => 'Award',
'fas fa-smile' => 'Smile',
'fas fa-bolt' => 'Bolt',
'fas fa-recycle' => 'Recycle',
'fas fa-hand-holding-heart'=> 'Care',
'fas fa-crown' => 'Crown',
'fas fa-gem' => 'Gem',
'fas fa-thumbs-up' => 'Thumbs Up',
];
?>
<div class="page-header">
<h1 class="page-title">Splash Box</h1>
<button class="btn btn-primary" onclick="openSplashModal()">
<i class="fas fa-plus"></i> Add Splash
</button>
</div>
<?php if (hasFlash('success')): ?>
<div class="alert alert-success"><i class="fas fa-check-circle"></i> <?= getFlash('success') ?></div>
<?php endif; ?>
<div class="admin-card" style="margin-bottom:1.5rem">
<div class="admin-card-body" style="padding:.85rem 1.5rem">
<p class="text-muted" style="margin:0"><i class="fas fa-info-circle"></i>
Drag rows to reorder — saves automatically.
<?= count($splashes) ?> block<?= count($splashes) !== 1 ? 's' : '' ?> total.
Homepage scrolls horizontally when more than 4 are active.
Each block can show an icon <em>or</em> a custom image.</p>
</div>
</div>
<!-- Live Preview -->
<div class="admin-card" style="margin-bottom:1.5rem">
<div class="admin-card-header"><h3><i class="fas fa-eye"></i> Homepage Preview</h3></div>
<div class="admin-card-body" style="background:var(--admin-bg);border-radius:var(--radius-md);padding:0;overflow:hidden">
<div id="splashPreview" style="display:flex;gap:1.5rem;overflow-x:auto;padding:2rem;scrollbar-width:thin">
<?php foreach ($splashes as $sp): if (!$sp['is_active']) continue; ?>
<div style="min-width:190px;text-align:center;padding:1.5rem 1rem;background:var(--admin-card);border-radius:var(--radius-lg);flex-shrink:0;box-shadow:0 1px 4px rgba(0,0,0,.08)">
<div style="width:56px;height:56px;background:linear-gradient(135deg,var(--admin-primary),#c4420f);border-radius:12px;display:flex;align-items:center;justify-content:center;margin:0 auto .75rem;color:#fff;font-size:1.35rem;overflow:hidden">
<?php if (!empty($sp['image_url'])): ?>
<img src="<?= htmlspecialchars($sp['image_url']) ?>" style="width:100%;height:100%;object-fit:cover" alt="">
<?php else: ?>
<i class="<?= htmlspecialchars($sp['icon']) ?>"></i>
<?php endif; ?>
</div>
<div style="font-weight:600;font-size:.9rem;margin-bottom:.35rem"><?= htmlspecialchars($sp['title']) ?></div>
<div style="font-size:.78rem;color:var(--admin-text-muted);line-height:1.4"><?= htmlspecialchars($sp['description'] ?? '') ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- Table -->
<div class="admin-card">
<div class="admin-card-body" style="padding:0">
<?php if (empty($splashes)): ?>
<div class="text-center text-muted" style="padding:3rem">No splash blocks yet. Click <strong>Add Splash</strong> to get started.</div>
<?php else: ?>
<table class="admin-table">
<thead>
<tr>
<th style="width:30px"></th>
<th style="width:60px">Visual</th>
<th>Title</th>
<th>Description</th>
<th style="width:60px">Order</th>
<th style="width:80px">Status</th>
<th style="width:100px">Actions</th>
</tr>
</thead>
<tbody id="splashTbody">
<?php foreach ($splashes as $sp): ?>
<tr data-id="<?= $sp['splash_id'] ?>">
<td style="color:var(--admin-text-muted);text-align:center;cursor:grab"><i class="fas fa-grip-vertical"></i></td>
<td>
<div style="width:42px;height:42px;background:linear-gradient(135deg,var(--admin-primary),#c4420f);border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff;overflow:hidden">
<?php if (!empty($sp['image_url'])): ?>
<img src="<?= htmlspecialchars($sp['image_url']) ?>" style="width:100%;height:100%;object-fit:cover" alt="">
<?php else: ?>
<i class="<?= htmlspecialchars($sp['icon']) ?>"></i>
<?php endif; ?>
</div>
</td>
<td><strong><?= htmlspecialchars($sp['title']) ?></strong></td>
<td style="max-width:260px;color:var(--admin-text-muted);font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
<?= htmlspecialchars($sp['description'] ?? '') ?>
</td>
<td class="sort-cell"><?= $sp['sort_order'] ?></td>
<td>
<?= $sp['is_active']
? '<span class="badge badge-success">Active</span>'
: '<span class="badge badge-error">Hidden</span>' ?>
</td>
<td>
<button class="btn btn-sm btn-secondary" onclick='openSplashModal(<?= json_encode($sp) ?>)' title="Edit">
<i class="fas fa-edit"></i>
</button>
<form method="POST" style="display:inline">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="splash_id" value="<?= $sp['splash_id'] ?>">
<button type="submit" class="btn btn-sm btn-danger" data-confirm="Delete this splash block?">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<!-- ── Modal ──────────────────────────────────────── -->
<div class="modal-overlay" id="splashModal">
<div class="modal" style="max-width:620px;width:95vw">
<div class="modal-header">
<h3 class="modal-title" id="splashModalTitle">Add Splash Block</h3>
<button type="button" class="modal-close" onclick="Modal.close('splashModal')">&times;</button>
</div>
<form method="POST" id="splashForm">
<div class="modal-body">
<input type="hidden" name="action" id="splashAction" value="create">
<input type="hidden" name="splash_id" id="splashId">
<input type="hidden" name="image_url" id="splashImageUrl">
<!-- Image upload zone -->
<div class="form-group">
<label class="form-label">Image <span class="text-muted" style="font-weight:400">(optional — overrides icon when set)</span></label>
<div id="dropZone" style="border:2px dashed var(--color-border);border-radius:var(--radius-md);padding:1.5rem;text-align:center;cursor:pointer;transition:all .2s;position:relative">
<div id="dropPlaceholder">
<i class="fas fa-cloud-upload-alt" style="font-size:2rem;color:var(--admin-text-muted);display:block;margin-bottom:.5rem"></i>
<div style="font-size:.875rem;color:var(--admin-text-muted)">Drag &amp; drop an image here, or <span style="color:var(--admin-primary);font-weight:600">browse</span></div>
<div style="font-size:.75rem;color:var(--admin-text-muted);margin-top:.25rem">JPG, PNG, WebP, GIF · Max 5 MB</div>
</div>
<div id="dropPreview" style="display:none">
<img id="dropPreviewImg" src="" alt="" style="max-height:120px;max-width:100%;border-radius:var(--radius-md);margin-bottom:.5rem">
<div>
<button type="button" class="btn btn-sm btn-danger" onclick="clearImage(event)">
<i class="fas fa-times"></i> Remove image
</button>
</div>
</div>
<input type="file" id="dropInput" accept="image/*" style="position:absolute;inset:0;opacity:0;cursor:pointer">
<div id="dropUploading" style="display:none;color:var(--admin-text-muted)"><i class="fas fa-spinner fa-spin"></i> Uploading…</div>
</div>
</div>
<!-- Icon picker -->
<div class="form-group" id="iconGroup">
<label class="form-label">Icon <span class="text-muted" style="font-weight:400">(used when no image is set)</span></label>
<div style="display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:.6rem">
<?php foreach ($iconOptions as $cls => $label): ?>
<button type="button" class="icon-opt btn btn-secondary" data-icon="<?= $cls ?>" title="<?= $label ?>"
style="width:40px;height:40px;padding:0;display:flex;align-items:center;justify-content:center"
onclick="pickIcon('<?= $cls ?>')">
<i class="<?= $cls ?>"></i>
</button>
<?php endforeach; ?>
</div>
<input type="text" name="icon" id="splashIcon" class="form-input" placeholder="Custom FA class e.g. fas fa-mug-hot" value="fas fa-star">
</div>
<div class="form-group">
<label class="form-label">Title *</label>
<input type="text" name="title" id="splashTitle" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea name="description" id="splashDesc" class="form-input" rows="3" style="resize:vertical"></textarea>
</div>
<div style="display:flex;gap:1rem">
<div class="form-group" style="flex:1">
<label class="form-label">Sort Order</label>
<input type="number" name="sort_order" id="splashOrder" class="form-input" value="0" min="0">
</div>
<div class="form-group" id="splashStatusGroup" style="display:none;flex:1">
<label class="form-label">Status</label>
<label style="display:flex;align-items:center;gap:.5rem;margin-top:.6rem;cursor:pointer">
<input type="checkbox" name="is_active" id="splashActive" checked> Active
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="Modal.close('splashModal')">Cancel</button>
<button type="submit" class="btn btn-primary" id="splashSubmitBtn">Add Splash</button>
</div>
</form>
</div>
</div>
<style>
.icon-opt.selected { background:var(--admin-primary)!important;color:#fff!important;border-color:var(--admin-primary)!important; }
#dropZone.drag-active { border-color:var(--admin-primary);background:rgba(255,94,26,.04); }
#splashTbody tr { cursor:grab; }
#splashTbody tr.drag-over { background:rgba(255,94,26,.06); }
</style>
<script>
/* ── Icon picker ─────────────────────────────────── */
function pickIcon(cls) {
document.getElementById('splashIcon').value = cls;
document.querySelectorAll('.icon-opt').forEach(function(b) {
b.classList.toggle('selected', b.dataset.icon === cls);
});
}
document.getElementById('splashIcon').addEventListener('input', function() {
document.querySelectorAll('.icon-opt').forEach(function(b) {
b.classList.toggle('selected', b.dataset.icon === this.value);
}.bind(this));
});
/* ── Image drop zone ─────────────────────────────── */
var dropZone = document.getElementById('dropZone');
var dropInput = document.getElementById('dropInput');
['dragenter','dragover'].forEach(function(ev) {
dropZone.addEventListener(ev, function(e) { e.preventDefault(); dropZone.classList.add('drag-active'); });
});
['dragleave','drop'].forEach(function(ev) {
dropZone.addEventListener(ev, function(e) { e.preventDefault(); dropZone.classList.remove('drag-active'); });
});
dropZone.addEventListener('drop', function(e) { handleFile(e.dataTransfer.files[0]); });
dropInput.addEventListener('change', function() { if (this.files[0]) handleFile(this.files[0]); });
function handleFile(file) {
if (!file) return;
var allowed = ['image/jpeg','image/png','image/gif','image/webp'];
if (!allowed.includes(file.type)) { alert('Please use JPG, PNG, WebP or GIF.'); return; }
if (file.size > 5 * 1024 * 1024) { alert('File too large (max 5 MB).'); return; }
document.getElementById('dropPlaceholder').style.display = 'none';
document.getElementById('dropPreview').style.display = 'none';
document.getElementById('dropUploading').style.display = 'block';
var fd = new FormData();
fd.append('image', file);
fetch('/admin/api/upload-splash.php', { method: 'POST', body: fd })
.then(function(r) { return r.json(); })
.then(function(data) {
document.getElementById('dropUploading').style.display = 'none';
if (data.error) { alert(data.error); showDropPlaceholder(); return; }
document.getElementById('splashImageUrl').value = data.url;
document.getElementById('dropPreviewImg').src = data.url;
document.getElementById('dropPreview').style.display = 'block';
})
.catch(function() { document.getElementById('dropUploading').style.display='none'; showDropPlaceholder(); });
}
function showDropPlaceholder() {
document.getElementById('dropPlaceholder').style.display = 'block';
document.getElementById('dropPreview').style.display = 'none';
}
function clearImage(e) {
e.stopPropagation();
document.getElementById('splashImageUrl').value = '';
document.getElementById('dropInput').value = '';
showDropPlaceholder();
}
/* ── Open modal ──────────────────────────────────── */
function openSplashModal(sp) {
var isEdit = !!sp;
document.getElementById('splashModalTitle').textContent = isEdit ? 'Edit Splash Block' : 'Add Splash Block';
document.getElementById('splashSubmitBtn').textContent = isEdit ? 'Save Changes' : 'Add Splash';
document.getElementById('splashAction').value = isEdit ? 'update' : 'create';
document.getElementById('splashId').value = isEdit ? sp.splash_id : '';
document.getElementById('splashTitle').value = isEdit ? sp.title : '';
document.getElementById('splashDesc').value = isEdit ? (sp.description || '') : '';
document.getElementById('splashOrder').value = isEdit ? sp.sort_order : 0;
document.getElementById('splashActive').checked = isEdit ? !!parseInt(sp.is_active) : true;
document.getElementById('splashStatusGroup').style.display = isEdit ? '' : 'none';
pickIcon(isEdit && sp.icon ? sp.icon : 'fas fa-star');
// image
var imgUrl = isEdit ? (sp.image_url || '') : '';
document.getElementById('splashImageUrl').value = imgUrl;
document.getElementById('dropInput').value = '';
if (imgUrl) {
document.getElementById('dropPreviewImg').src = imgUrl;
document.getElementById('dropPreview').style.display = 'block';
document.getElementById('dropPlaceholder').style.display = 'none';
document.getElementById('dropUploading').style.display = 'none';
} else {
showDropPlaceholder();
document.getElementById('dropUploading').style.display = 'none';
}
Modal.open('splashModal');
}
/* ── Drag-to-reorder rows ────────────────────────── */
(function() {
var tbody = document.getElementById('splashTbody');
if (!tbody) return;
var dragging = null;
tbody.querySelectorAll('tr').forEach(function(row) {
row.draggable = true;
row.addEventListener('dragstart', function() { dragging = this; this.style.opacity = '.4'; });
row.addEventListener('dragend', function() { this.style.opacity = ''; dragging = null; saveOrder(); });
row.addEventListener('dragover', function(e) { e.preventDefault(); this.classList.add('drag-over'); });
row.addEventListener('dragleave', function() { this.classList.remove('drag-over'); });
row.addEventListener('drop', function(e) {
e.preventDefault(); this.classList.remove('drag-over');
if (dragging && dragging !== this) {
var rows = Array.from(tbody.querySelectorAll('tr'));
if (rows.indexOf(dragging) < rows.indexOf(this)) this.after(dragging);
else this.before(dragging);
}
});
});
function saveOrder() {
var ids = Array.from(tbody.querySelectorAll('tr')).map(function(r) { return r.dataset.id; });
fetch('/admin/splashes.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'action=reorder&order=' + encodeURIComponent(JSON.stringify(ids))
});
tbody.querySelectorAll('tr').forEach(function(r, i) {
r.querySelector('.sort-cell').textContent = i + 1;
});
}
})();
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>