Files
PPve/admin-dashboard.html
T
Tomas Dvorak 13d517731a f
2025-05-29 11:43:22 +02:00

2662 lines
95 KiB
HTML

<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - PP Kunovice</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.tailwindcss.com"></script>
<style>
:root {
--primary-color: #4a6cf7;
--primary-hover: #3a56d4;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--warning-color: #ffc107;
--info-color: #17a2b8;
--light-color: #f8f9fa;
--dark-color: #212529;
--border-color: #e9ecef;
--border-radius: 8px;
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
* {
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Roboto, -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
padding: 0;
background-color: #f8f9fa;
color: #333;
line-height: 1.6;
}
.header {
background-color: #fff;
color: var(--dark-color);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: var(--box-shadow);
position: sticky;
top: 0;
z-index: 1000;
}
.header h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: var(--primary-color);
}
.logout-btn {
background-color: transparent;
border: 1px solid var(--primary-color);
color: var(--primary-color);
padding: 0.5rem 1.2rem;
border-radius: var(--border-radius);
cursor: pointer;
font-weight: 500;
transition: var(--transition);
}
.logout-btn:hover {
background-color: var(--primary-color);
color: white;
}
.container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1.5rem;
}
.dashboard-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.card {
background-color: white;
border-radius: var(--border-radius);
padding: 1.75rem;
box-shadow: var(--box-shadow);
transition: var(--transition);
border: 1px solid var(--border-color);
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
}
.card h3 {
margin-top: 0;
color: var(--dark-color);
font-weight: 600;
font-size: 1.25rem;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--dark-color);
font-size: 0.95rem;
}
.form-group input[type="text"],
.form-group input[type="url"],
.form-group input[type="number"],
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
font-size: 1rem;
transition: var(--transition);
box-shadow: inset 0 1px 2px rgba(0,0,0,0.05);
}
.form-group input[type="text"]:focus,
.form-group input[type="url"]:focus,
.form-group input[type="number"]:focus,
.form-group textarea:focus,
.form-group select:focus {
border-color: var(--primary-color);
outline: none;
box-shadow: 0 0 0 3px rgba(74, 108, 247, 0.15);
}
.form-group textarea {
min-height: 120px;
resize: vertical;
line-height: 1.5;
}
.color-preview {
display: inline-block;
width: 30px;
height: 30px;
border: 1px solid var(--border-color);
border-radius: 50%;
vertical-align: middle;
margin-left: 10px;
box-shadow: inset 0 1px 2px rgba(0,0,0,0.1);
}
.color-picker {
margin-left: 10px;
vertical-align: middle;
height: 30px;
padding: 0;
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
}
.style-presets {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 15px 0;
}
.style-preset {
padding: 8px 16px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
cursor: pointer;
font-size: 0.9rem;
transition: var(--transition);
background-color: white;
font-weight: 500;
}
.style-preset:hover {
background-color: var(--light-color);
border-color: var(--secondary-color);
transform: translateY(-2px);
}
.banner-preview {
margin: 2rem 0;
padding: 0;
border: 2px dashed var(--border-color);
border-radius: var(--border-radius);
background-color: #fff;
display: none;
position: relative;
overflow: hidden;
min-height: 180px;
transition: var(--transition);
box-shadow: var(--box-shadow);
width: 100%;
max-width: 100%;
}
.banner-preview:hover {
border-color: var(--primary-color);
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
.banner-preview::before {
content: 'Náhled banneru';
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(255,255,255,0.8);
padding: 4px 10px;
border-radius: 4px;
font-size: 0.8rem;
color: var(--secondary-color);
z-index: 3;
font-weight: 500;
}
.banner-preview img {
max-width: 100%;
max-height: 300px;
object-fit: contain;
display: block;
margin: 0 auto;
}
.banner-preview.with-image {
min-height: 220px;
}
.banner-preview-content {
position: relative;
z-index: 2;
text-align: center;
padding: 20px;
margin: 0;
width: 100%;
box-sizing: border-box;
display: block;
}
.banner-preview-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
opacity: 0.7;
z-index: 1;
transition: var(--transition);
}
.banner-preview-text {
position: relative;
z-index: 2;
margin: 0;
padding: 0;
line-height: 1.5;
word-wrap: break-word;
}
.color-picker-container {
display: flex;
align-items: center;
gap: 10px;
}
.color-picker {
vertical-align: middle;
margin-right: 8px;
cursor: pointer;
width: 40px;
height: 40px;
border: none;
border-radius: 8px;
overflow: hidden;
padding: 0;
background: none;
}
.image-upload-container {
margin: 15px 0;
padding: 15px;
border: 1px dashed #ddd;
border-radius: 4px;
text-align: center;
}
.image-preview {
max-width: 200px;
max-height: 150px;
margin: 10px auto;
display: block;
}
.card p {
color: #666;
margin-bottom: 0;
}
/* Form Actions */
.form-actions {
margin-top: 2.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem;
border: 1px solid transparent;
border-radius: var(--border-radius);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
position: relative;
overflow: hidden;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.btn:active {
transform: translateY(0);
}
.btn i {
margin-right: 0.5rem;
font-size: 0.9em;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: var(--primary-hover);
border-color: var(--primary-hover);
}
.btn-secondary {
background-color: var(--secondary-color);
color: white;
border-color: var(--secondary-color);
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #545b62;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
border-color: var(--danger-color);
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
}
.btn:disabled {
opacity: 0.65;
cursor: not-allowed;
transform: none;
}
/* Notifications */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 16px 24px;
border-radius: var(--border-radius);
color: white;
display: flex;
align-items: center;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
z-index: 1000;
animation: fadeIn 0.3s, fadeOut 0.3s 2.7s forwards;
min-width: 300px;
max-width: 450px;
}
.notification i {
margin-right: 12px;
font-size: 1.2rem;
}
.notification.info {
background-color: var(--info-color);
border-left: 4px solid #117a8b;
}
.notification.success {
background-color: var(--success-color);
border-left: 4px solid #1e7e34;
}
.notification.warning {
background-color: var(--warning-color);
color: #212529;
border-left: 4px solid #d39e00;
}
.notification.error {
background-color: var(--danger-color);
border-left: 4px solid #bd2130;
}
.notification.fade-out {
animation: fadeOut 0.3s forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateY(-20px);
}
}
/* Additional responsive styles */
@media (max-width: 768px) {
.container {
padding: 0 1rem;
}
.card {
padding: 1.25rem;
}
.notification {
left: 20px;
right: 20px;
max-width: calc(100% - 40px);
}
.form-actions {
flex-direction: column;
}
.form-actions .btn {
width: 100%;
margin-bottom: 0.5rem;
}
}
.drag-drop-area {
border: 2px dashed var(--border-color);
border-radius: var(--border-radius);
padding: 30px 20px;
text-align: center;
background-color: #f8f9fa;
cursor: pointer;
transition: var(--transition);
margin-bottom: 15px;
position: relative;
}
.drag-drop-area:hover, .drag-drop-area.dragover {
border-color: var(--primary-color);
background-color: rgba(74, 108, 247, 0.05);
}
.drag-drop-message {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--secondary-color);
}
.drag-drop-message i {
font-size: 2.5rem;
margin-bottom: 15px;
color: var(--primary-color);
}
.drag-drop-message p {
margin: 0;
font-size: 1rem;
}
.image-actions {
display: flex;
gap: 10px;
}
.image-preview {
max-width: 100%;
max-height: 200px;
border-radius: 4px;
margin-top: 10px;
display: none;
}
.banner-preview.with-image {
min-height: 220px;
}
.image-position-options {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.image-position-btn {
padding: 6px 12px;
background-color: #f8f9fa;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
font-size: 0.9rem;
cursor: pointer;
transition: var(--transition);
}
.image-position-btn:hover {
background-color: #e9ecef;
}
.image-position-btn.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.draggable-image {
cursor: move;
position: relative;
z-index: 10;
transition: none;
user-select: none;
}
.draggable-image.dragging {
opacity: 0.8;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
}
.banner-preview-container {
margin: 20px 0;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
.banner-preview {
width: 100%;
min-height: 200px;
background: #ffffff;
border: 1px dashed #adb5bd;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.banner-preview img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.banner-preview .banner-text {
padding: 20px;
text-align: center;
z-index: 2;
}
/* Upload Box Styles */
.upload-box {
border: 2px dashed #dee2e6;
border-radius: 8px;
background-color: #f8f9fa;
transition: all 0.3s ease;
overflow: hidden;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.upload-box.dragover {
border-color: #0d6efd;
background-color: rgba(13, 110, 253, 0.05);
}
.upload-prompt {
padding: 2rem;
max-width: 320px;
margin: 0 auto;
}
.upload-icon {
color: #adb5bd;
transition: color 0.2s;
}
.upload-box:hover .upload-icon {
color: #6c757d;
}
/* Image Preview Styles */
.image-preview {
width: 100%;
height: 100%;
padding: 1rem;
}
.preview-container {
position: relative;
width: 100%;
height: 100%;
border-radius: 6px;
overflow: hidden;
background-color: #f1f3f5;
display: flex;
align-items: center;
justify-content: center;
min-height: 180px;
}
.preview-container img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-overlay {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.2s;
display: flex;
gap: 0.5rem;
}
.preview-container:hover .preview-overlay {
opacity: 1;
}
.preview-overlay .btn {
width: 36px;
height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* Position Controls */
.position-controls .btn-group {
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.position-controls .btn {
transition: all 0.2s;
font-weight: 500;
}
.position-controls .btn.active {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.upload-box {
min-height: 160px;
}
.upload-prompt {
padding: 1.5rem;
}
.preview-overlay {
opacity: 1;
bottom: 0.5rem;
}
}
</style>
</head>
<body>
<div class="header">
<h1>Admin Dashboard</h1>
<button class="logout-btn" id="logoutBtn">Odhlásit se</button>
</div>
<div class="container">
<h2>Vítejte v administraci</h2>
<div class="card" style="margin: 2rem auto; max-width: 1000px;">
<h3>Správa banneru</h3>
<div class="form-group">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="bannerVisibility" checked>
<label class="form-check-label" for="bannerVisibility">Viditelnost banneru</label>
</div>
<input type="hidden" id="bannerVisible" name="isVisible" value="true">
<label for="bannerText">Text banneru:</label>
<textarea id="bannerText" class="form-control" rows="3" placeholder="Zadejte text banneru..."></textarea>
<div class="mt-3">
<label for="bannerLink">Odkaz (volitelný):</label>
<input type="url" id="bannerLink" class="form-control" placeholder="https://example.com">
</div>
</div>
<div class="card-body">
<form id="bannerForm">
<!-- Templates Section -->
<div class="mb-6">
<h3 class="text-lg font-medium text-gray-900 mb-3">Vyberte šablonu</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mb-6" id="templateGrid">
<!-- Templates will be inserted here by JavaScript -->
</div>
</div>
<div class="form-group">
<label class="form-label fw-bold mb-2">Obrázek banneru</label>
<!-- Upload Box -->
<div id="dropArea" class="upload-box">
<input type="file" id="fileElem" accept="image/*" class="d-none">
<!-- Upload Prompt -->
<div id="uploadPrompt" class="upload-prompt text-center p-4">
<div class="upload-icon mb-3">
<i class="fas fa-image fa-3x text-muted"></i>
</div>
<h5 class="mb-2">Přetáhněte obrázek sem</h5>
<p class="text-muted small mb-3">Nebo</p>
<button type="button" class="btn btn-primary btn-sm" id="uploadBtn">
<i class="fas fa-upload me-2"></i>Vybrat soubor
</button>
<p class="small text-muted mt-2 mb-0">Podporované formáty: JPG, PNG, GIF (max. 5MB)</p>
</div>
<!-- Image Preview -->
<div id="imagePreview" class="image-preview" style="display: none;">
<div class="preview-container">
<div class="banner-image-container" style="
display: block;
max-width: 100%;
text-align: center;
">
<img
src=""
style="
max-width: 100%;
max-height: 200px;
width: auto;
height: auto;
object-fit: contain;
border-radius: 8px;
"
alt="Banner obrázek"
class="banner-image"
onerror="console.error('Failed to load banner image:', this.src)"
draggable="false"
>
</div>
<div class="preview-overlay">
<button type="button" class="btn btn-light btn-sm rounded-circle me-1" id="changeImageBtn">
<i class="fas fa-sync-alt"></i>
</button>
<button type="button" class="btn btn-light btn-sm rounded-circle" id="removeImageBtn">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Image Position Controls -->
<div class="position-controls mt-3">
<label class="form-label d-block">Pozice obrázku</label>
<div class="btn-group w-100" role="group">
<button type="button" class="btn btn-outline-primary position-btn active" data-position="left">
<i class="fas fa-align-left me-1"></i> Vlevo
</button>
<button type="button" class="btn btn-outline-secondary position-btn" data-position="center">
<i class="fas fa-align-center me-1"></i> Uprostřed
</button>
<button type="button" class="btn btn-outline-secondary position-btn" data-position="right">
<i class="fas fa-align-right me-1"></i> Vpravo
</button>
</div>
</div>
</div>
</form>
<div class="form-section">
<h3>Náhled banneru</h3>
<div id="bannerPreviewContainer" class="banner-preview-container">
<div id="bannerPreview" class="banner-preview">
<div id="bannerPreviewContent" style="width: 100%; height: 100%;"></div>
</div>
</div>
<h3>Nastavení banneru</h3>
<div class="form-actions">
<button type="submit" id="saveBannerBtn" class="btn btn-primary">
<i class="fas fa-save"></i> Uložit banner
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Get token and check authentication
const token = localStorage.getItem('token');
if (!token) {
window.location.href = '/login.html';
}
// Show notification to user
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
// Set icon based on notification type
let icon = 'info-circle';
if (type === 'success') icon = 'check-circle';
else if (type === 'error') icon = 'exclamation-circle';
else if (type === 'warning') icon = 'exclamation-triangle';
notification.innerHTML = `
<i class="fas fa-${icon}"></i>
<span>${message}</span>
`;
document.body.appendChild(notification);
// Auto-remove notification after delay
const delay = type === 'error' ? 5000 : 3000;
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => notification.remove(), 300);
}, delay);
}
// Override fetch to include token (but NOT for FormData requests)
const originalFetch = window.fetch;
window.fetch = async function(resource, init = {}) {
// Add token to headers if it's an API request
if (typeof resource === 'string' && resource.startsWith('/api/')) {
const headers = new Headers(init.headers || {});
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
// Only set content type if not FormData (FormData sets its own)
if (!headers.has('Content-Type') && init.body && !(init.body instanceof FormData)) {
headers.set('Content-Type', 'application/json');
}
init.headers = headers;
}
return originalFetch(resource, init);
};
// Image handling - Drag and Drop functionality
let dragDropArea, uploadImageBtn, bannerImageElement;
// Prevent default behavior for drag events
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Visual feedback when dragging over the area
function highlight() {
if (dragDropArea) dragDropArea.classList.add('dragover');
}
function unhighlight() {
if (dragDropArea) dragDropArea.classList.remove('dragover');
}
// Initialize drag and drop functionality
function initDragAndDrop() {
dragDropArea = document.getElementById('dragDropArea');
uploadImageBtn = document.getElementById('uploadImageBtn');
bannerImage = document.getElementById('bannerImage');
if (!dragDropArea || !uploadImageBtn || !bannerImage) return;
// Click on drag area to select file
dragDropArea.addEventListener('click', function() {
bannerImage.click();
});
// Click on upload button to select file
uploadImageBtn.addEventListener('click', function() {
bannerImage.click();
});
// Prevent default behavior for drag events
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dragDropArea.addEventListener(eventName, preventDefaults, false);
});
// Visual feedback when dragging over the area
['dragenter', 'dragover'].forEach(eventName => {
dragDropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dragDropArea.addEventListener(eventName, unhighlight, false);
});
// Handle dropped files
dragDropArea.addEventListener('drop', handleDrop, false);
}
// Update banner preview
function updateBannerPreview() {
const bannerPreview = document.getElementById('bannerPreview');
if (!bannerPreview) return;
// Get current values
const text = document.getElementById('bannerText').value || 'Náhled textu banneru';
const bgColor = document.getElementById('bannerBgColor')?.value || '#f8f9fa';
const textColor = document.getElementById('bannerTextColor')?.value || '#212529';
const textAlign = document.getElementById('bannerTextAlign')?.value || 'center';
const fontSize = document.getElementById('bannerFontSize')?.value || '1rem';
const padding = document.getElementById('bannerPadding')?.value || '20px';
const margin = document.getElementById('bannerMargin')?.value || '10px 0';
const borderRadius = document.getElementById('bannerBorderRadius')?.value || '4px';
const isVisible = document.getElementById('bannerVisibility')?.checked !== false;
// Get image if exists
const imagePreview = document.getElementById('bannerImagePreview');
const hasImage = imagePreview && imagePreview.src && !imagePreview.classList.contains('d-none');
// Build preview HTML
let previewHTML = `
<div class="banner-preview-content" style="
background: ${bgColor};
color: ${textColor};
text-align: ${textAlign};
font-size: ${fontSize};
padding: ${padding};
margin: ${margin};
border-radius: ${borderRadius};
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: ${textAlign};
position: relative;
overflow: hidden;
">
<div class="banner-text">${text}</div>
`;
// Add image if exists
if (hasImage) {
const imagePosition = document.querySelector('.image-pos-btn.active')?.dataset.pos || 'center';
let imageStyle = 'max-width: 100%; max-height: 100%; object-fit: contain;';
// Apply position based on selection
switch(imagePosition) {
case 'left':
imageStyle += 'margin-right: auto;';
break;
case 'right':
imageStyle += 'margin-left: auto;';
break;
case 'center':
default:
imageStyle += 'margin: 0 auto;';
break;
}
previewHTML += `
<img src="${imagePreview.src}" alt="Banner preview" style="${imageStyle}">
`;
}
previewHTML += '</div>';
// Update preview
bannerPreview.innerHTML = previewHTML;
// No custom positioning, always right-aligned
}
// Initialize template object if not exists
let template = {
containerStyle: '',
textStyle: '',
bgColor: '#f8f9fa',
textColor: '#212529',
textAlign: 'left',
fontSize: 16,
padding: 20,
margin: 20,
borderRadius: 8
};
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
// Initialize banner preview elements
bannerVisible = document.getElementById('bannerVisibility');
bannerBgColor = document.getElementById('bannerBgColor');
bannerTextColor = document.getElementById('bannerTextColor');
bannerText = document.getElementById('bannerText');
bannerTextAlign = document.getElementById('bannerTextAlign');
bannerFontSize = document.getElementById('bannerFontSize');
bannerPadding = document.getElementById('bannerPadding');
bannerMargin = document.getElementById('bannerMargin');
bannerBorderRadius = document.getElementById('bannerBorderRadius');
bannerPreview = document.getElementById('bannerPreview');
// Initialize drag and drop and image upload
initDragAndDrop();
// Set up file input change event
const bannerImageInput = document.getElementById('bannerImage');
if (bannerImageInput) {
bannerImageInput.addEventListener('change', handleImageUpload);
}
// Set up event listeners for preview updates
const previewInputs = [
bannerVisible, bannerBgColor, bannerTextColor, bannerText,
bannerTextAlign, bannerFontSize, bannerPadding, bannerMargin, bannerBorderRadius
];
previewInputs.forEach(input => {
if (input) {
input.addEventListener('input', updateBannerPreview);
input.addEventListener('change', updateBannerPreview);
}
});
// Initialize upload functionality
const uploadPrompt = document.getElementById('uploadPrompt');
const imagePreview = document.getElementById('imagePreview');
const bannerImagePreview = document.getElementById('bannerImagePreview');
const fileInput = document.getElementById('fileElem');
const uploadBtn = document.getElementById('uploadBtn');
const changeImageBtn = document.getElementById('changeImageBtn');
const removeImageBtn = document.getElementById('removeImageBtn');
const dropArea = document.getElementById('dropArea');
// Image is always on the right side
const rightPositionBtn = document.querySelector('.image-pos-btn[data-pos="right"], .position-btn[data-pos="right"]');
if (rightPositionBtn) {
rightPositionBtn.classList.add('active', 'btn-primary');
rightPositionBtn.classList.remove('btn-outline-secondary');
}
// Handle file selection
function handleFileSelect(file) {
if (!file) return;
// Check file type
const validTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!validTypes.includes(file.type)) {
showNotification('Nepodporovaný formát souboru. Povolené formáty: JPG, PNG, GIF', 'error');
return;
}
// Check file size (5MB max)
if (file.size > 5 * 1024 * 1024) {
showNotification('Soubor je příliš velký. Maximální velikost je 5MB.', 'error');
return;
}
// Show preview
const reader = new FileReader();
reader.onload = function(e) {
try {
currentImage = e.target.result;
const bannerImagePreview = document.getElementById('bannerImagePreview');
const uploadPrompt = document.getElementById('uploadPrompt');
const imagePreview = document.getElementById('imagePreview');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
if (bannerImagePreview) {
bannerImagePreview.src = currentImage;
bannerImagePreview.style.display = 'block';
bannerImagePreview.style.maxWidth = '100%';
bannerImagePreview.style.maxHeight = '200px';
bannerImagePreview.style.objectFit = 'contain';
}
if (uploadPrompt) {
uploadPrompt.style.display = 'none';
}
if (imagePreview) {
imagePreview.style.display = 'block';
}
if (imagePreviewContainer) {
imagePreviewContainer.style.display = 'block';
}
// Initialize or update the banner preview
updateBannerPreview();
} catch (error) {
console.error('Error handling image preview:', error);
showNotification('Chyba při načítání náhledu obrázku', 'error');
}
};
reader.onerror = function() {
console.error('Error reading file');
showNotification('Chyba při čtení souboru', 'error');
};
reader.readAsDataURL(file);
}
// Handle file input change
fileInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
handleFileSelect(this.files[0]);
}
});
// Handle upload button click
uploadBtn.addEventListener('click', () => fileInput.click());
// Handle change image button
if (changeImageBtn) {
changeImageBtn.addEventListener('click', () => fileInput.click());
}
// Handle remove image button
if (removeImageBtn) {
removeImageBtn.addEventListener('click', function() {
currentImage = null;
fileInput.value = '';
if (uploadPrompt) uploadPrompt.style.display = 'flex';
if (imagePreview) imagePreview.style.display = 'none';
updateBannerPreview();
});
}
// Handle drag and drop
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function highlight() {
dropArea.classList.add('dragover');
}
function unhighlight() {
dropArea.classList.remove('dragover');
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
dropArea.addEventListener('drop', function(e) {
const dt = e.dataTransfer;
const file = dt.files[0];
if (file) {
handleFileSelect(file);
}
});
// Set up save button
const saveBannerBtn = document.getElementById('saveBannerBtn');
if (saveBannerBtn) {
saveBannerBtn.addEventListener('click', saveBanner);
}
// Set up color input listeners
setupColorInputListeners();
// Initial preview update
updateBannerPreview();
// Load existing banner data
loadBanner();
});
// Prevent default drag behaviors
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Handle image upload
function handleImageUpload(event) {
const fileInput = event.target;
const file = fileInput.files[0];
if (!file) return;
// Check file type
const validImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'];
if (!validImageTypes.includes(file.type)) {
showNotification('Vyberte prosím soubor obrázku (JPG, PNG, GIF, SVG)', 'warning');
fileInput.value = ''; // Reset file input
return;
}
// Check file size (max 5MB)
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
showNotification('Maximální velikost souboru je 5MB', 'warning');
fileInput.value = ''; // Reset file input
return;
}
// Show loading state
const previewContainer = document.getElementById('imagePreviewContainer');
const dragDropMessage = document.querySelector('.drag-drop-message');
const bannerPreview = document.getElementById('bannerPreview');
if (previewContainer) {
previewContainer.style.display = 'block';
previewContainer.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Načítání...</span></div></div>';
}
// Hide the drag & drop message
if (dragDropMessage) {
dragDropMessage.style.display = 'none';
}
// Process the image
const reader = new FileReader();
reader.onload = function(e) {
// Update the image preview
const bannerImagePreview = document.getElementById('bannerImagePreview');
if (bannerImagePreview) {
bannerImagePreview.src = e.target.result;
bannerImagePreview.style.display = 'block';
bannerImagePreview.classList.remove('d-none');
// Show remove button
const removeBtn = document.getElementById('removeImageBtn');
if (removeBtn) removeBtn.style.display = 'inline-block';
// Update the current image
currentImage = e.target.result;
// Update the banner preview
updateBannerPreview();
// Show the preview container
if (previewContainer) {
previewContainer.style.display = 'block';
previewContainer.innerHTML = '';
previewContainer.appendChild(bannerImagePreview);
}
}
// Hide loading container
if (previewContainer) {
previewContainer.style.display = 'none';
}
// Show templates section if it exists
const bannerTemplates = document.getElementById('bannerTemplates');
if (bannerTemplates) {
bannerTemplates.style.display = 'block';
}
// Update banner preview with the new image
updateBannerPreview();
};
reader.onerror = function() {
showNotification('Při načítání obrázku došlo k chybě. Zkuste to prosím znovu.', 'error');
fileInput.value = ''; // Reset file input
// Reset preview
if (previewContainer) {
previewContainer.innerHTML = '';
previewContainer.style.display = 'none';
}
// Show drag & drop message again
if (dragDropMessage) {
dragDropMessage.style.display = 'flex';
}
};
reader.readAsDataURL(file);
}
// Logout functionality
document.getElementById('logoutBtn').addEventListener('click', function() {
localStorage.removeItem('token');
window.location.href = '/';
});
// DOM Elements
let bannerText, bannerVisible, bannerBgColor, bannerTextColor, bannerTextAlign, bannerFontSize,
bannerPadding, bannerMargin, bannerBorderRadius, bannerPreview, bannerPreviewContent,
bannerPreviewText, bannerPreviewBg, bgColorPreview, textColorPreview, saveBannerBtn,
stylePresets, currentImage = null, currentTemplate = 'modern-minimal';
// Preset styles
const presets = {
info: {
backgroundColor: '#cce5ff',
textColor: '#004085',
textAlign: 'left'
},
warning: {
backgroundColor: '#fff3cd',
textColor: '#856404',
textAlign: 'center'
},
success: {
backgroundColor: '#d4edda',
textColor: '#155724',
textAlign: 'center'
},
error: {
backgroundColor: '#f8d7da',
textColor: '#721c24',
textAlign: 'center'
}
};
// Variables for image positioning
let currentImagePosition = 'center'; // default position
let currentImageX = '0';
let currentImageY = '0';
async function loadBanner() {
try {
const response = await fetch('/api/banner');
if (!response.ok) throw new Error('Nepodařilo se načíst banner');
const data = await response.json();
console.log('Loaded banner data:', data);
if (data) {
// Update form fields
const bannerText = document.getElementById('bannerText');
const bannerLink = document.getElementById('bannerLink');
const bannerVisible = document.getElementById('bannerVisibility');
if (bannerText) bannerText.value = data.Text || '';
if (bannerLink) bannerLink.value = data.Link || '';
if (bannerVisible) {
bannerVisible.checked = data.Style?.IsVisible !== false;
// Update the hidden input for form submission
const hiddenVisible = document.getElementById('bannerVisible');
if (hiddenVisible) {
hiddenVisible.value = bannerVisible.checked ? 'true' : 'false';
}
// Force update visibility in preview
updateBannerPreview();
}
// Initialize image position variables once
currentImagePosition = data.Style?.ImagePosition || 'center';
currentImageX = data.Style?.ImageX || '0';
currentImageY = data.Style?.ImageY || '0';
// Apply the saved template if it exists
if (data.Style?.template && templateConfigs[data.Style.template]) {
// Apply the template
applyTemplate(data.Style.template);
// Update the active template in the UI
const templateItems = document.querySelectorAll('.template-item');
if (templateItems) {
templateItems.forEach(item => {
if (item && item.dataset.template === data.Style.template) {
item.classList.add('active');
} else if (item) {
item.classList.remove('active');
}
});
}
} else {
// Fallback to default template if none is set
applyTemplate('modern-minimal');
}
// Handle image
const bannerImgElement = document.getElementById('bannerImagePreview');
const uploadPrompt = document.getElementById('uploadPrompt');
const imagePreview = document.getElementById('imagePreview');
if (data.Image) {
currentImage = data.Image;
if (bannerImgElement) {
bannerImgElement.src = data.Image;
const bannerLinkValue = data.Link || '';
if (bannerLinkValue) {
bannerImgElement.style.cursor = 'pointer';
bannerImgElement.onclick = (e) => {
e.preventDefault();
window.open(bannerLinkValue, '_blank');
};
} else {
bannerImgElement.style.cursor = 'default';
bannerImgElement.onclick = null;
}
// Show the image preview and hide the upload prompt
if (uploadPrompt) uploadPrompt.style.display = 'none';
if (imagePreview) imagePreview.style.display = 'block';
}
// Update position if exists
if (data.Style?.ImagePosition) {
currentImagePosition = data.Style.ImagePosition;
// Update active state of position buttons
const positionButtons = document.querySelectorAll('.position-btn');
if (positionButtons) {
positionButtons.forEach(btn => {
if (btn) {
btn.classList.remove('active', 'btn-primary');
btn.classList.add('btn-outline-secondary');
if (btn.dataset.position === currentImagePosition) {
btn.classList.add('active', 'btn-primary');
btn.classList.remove('btn-outline-secondary');
}
}
});
}
}
// Update coordinates if they exist
if (data.Style?.ImageX !== undefined && data.Style?.ImageY !== undefined) {
currentImageX = data.Style.ImageX;
currentImageY = data.Style.ImageY;
// Update image preview position if it exists
if (bannerImgElement) {
bannerImgElement.style.left = `${currentImageX}px`;
bannerImgElement.style.top = `${currentImageY}px`;
// Activate custom position button if it exists
const customPosBtn = document.getElementById('customPosBtn');
if (customPosBtn) {
customPosBtn.classList.add('active', 'btn-primary');
customPosBtn.classList.remove('btn-outline-secondary');
}
}
}
// Show remove button if it exists
const removeBtn = document.getElementById('removeImageBtn');
if (removeBtn) {
removeBtn.style.display = 'block';
}
// Set remove image input if it exists
const removeImageInput = document.getElementById('removeImage');
if (removeImageInput) {
removeImageInput.value = 'false';
}
} else {
// No image in the saved banner
currentImage = null;
// Hide image preview and show upload prompt
if (imagePreview) {
imagePreview.style.display = 'none';
}
if (uploadPrompt) {
uploadPrompt.style.display = 'block';
}
// Hide remove button
const removeBtn = document.getElementById('removeImageBtn');
if (removeBtn) {
removeBtn.style.display = 'none';
}
// Set remove image input
const removeImageInput = document.getElementById('removeImage');
if (removeImageInput) {
removeImageInput.value = 'true';
}
}
// Update previews
updateColorPreviews();
updateBannerPreview();
}
} catch (error) {
console.error('Chyba při načítání banneru:', error);
showNotification('Chyba při načítání banneru', 'error');
}
}
// Add submission flag at the top of the script
let isSubmitting = false;
async function saveBanner(event) {
event.preventDefault();
// Prevent multiple submissions
if (isSubmitting) {
console.log('Form submission already in progress');
return;
}
isSubmitting = true;
const form = document.getElementById('bannerForm');
const formData = new FormData(form);
const submitButton = form.querySelector('button[type="submit"]');
const originalButtonText = submitButton ? submitButton.innerHTML : '';
try {
// Show loading state
if (submitButton) {
submitButton.disabled = true;
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Ukládám...';
}
// Add text and link to form data
const bannerText = document.getElementById('bannerText');
const bannerLink = document.getElementById('bannerLink');
const bannerVisibility = document.getElementById('bannerVisibility');
const bannerVisible = document.getElementById('bannerVisible');
// Update hidden field based on checkbox state
if (bannerVisibility) {
bannerVisible.value = bannerVisibility.checked ? 'true' : 'false';
}
formData.append('text', bannerText ? bannerText.value : '');
formData.append('link', bannerLink ? bannerLink.value : '');
formData.append('isVisible', bannerVisible ? bannerVisible.value : 'true');
// Always send the banner visibility
formData.append('isVisible', bannerVisible ? bannerVisible.value : 'true');
formData.append('style[isVisible]', bannerVisible ? bannerVisible.value : 'true');
// Ensure templateConfigs is defined and has the default template
if (typeof templateConfigs === 'undefined') {
templateConfigs = {};
}
// Get the current template or use default
const defaultTemplate = templateConfigs['modern-minimal'] || {
backgroundColor: '#f8f9fa',
textColor: '#212529',
textAlign: 'left',
fontSize: 16,
padding: 20,
margin: 20,
borderRadius: 8
};
const template = (currentTemplate && templateConfigs[currentTemplate]) || defaultTemplate;
// Add template name
formData.append('template', currentTemplate || 'modern-minimal');
// Get custom values if they exist
const customBg = bannerBgColorPicker?.value || '';
const customTextColor = bannerTextColorPicker?.value || '';
const imgPosition = document.getElementById('bannerImagePosition')?.value || 'right';
// Apply all template styles with proper fallbacks
const styles = {
// Background styles
background: (template.background || '').trim(),
backgroundColor: (customBg || template.backgroundColor || defaultTemplate.backgroundColor).trim(),
// Text styles
color: (customTextColor || template.textColor || defaultTemplate.textColor).trim(),
textColor: (customTextColor || template.textColor || defaultTemplate.textColor).trim(),
textAlign: (template.textAlign || defaultTemplate.textAlign).trim(),
fontSize: template.fontSize ? `${template.fontSize}px` : `${defaultTemplate.fontSize}px`,
// Layout styles
padding: template.padding ? `${template.padding}px` : `${defaultTemplate.padding}px`,
margin: template.margin ? `${template.margin}px` : `${defaultTemplate.margin}px`,
borderRadius: template.borderRadius ? `${template.borderRadius}px` : `${defaultTemplate.borderRadius}px`,
// Image position
imagePosition: imgPosition,
// Button styles (if template defines them)
buttonBackground: (template.buttonBackground || '#4a6cf7').trim(),
buttonTextColor: (template.buttonTextColor || '#ffffff').trim(),
buttonBorder: (template.buttonBorder || 'none').trim(),
// Container styles
containerStyle: (template.containerStyle || '').trim()
};
// Special handling for specific templates
if (currentTemplate === 'dark') {
styles.background = '#2d3748';
styles.backgroundColor = '#2d3748';
styles.color = '#e2e8f0';
styles.textColor = '#e2e8f0';
}
// Add all styles to form data
Object.entries(styles).forEach(([key, value]) => {
if (value !== undefined && value !== '') {
formData.append(`style[${key}]`, value);
}
});
// Add image position
const imagePosition = document.querySelector('.image-pos-btn.active')?.dataset.pos || 'center';
formData.append('style[imagePosition]', imagePosition);
// Add custom position if needed
if (imagePosition === 'custom') {
formData.append('style[imageX]', currentImageX || '0');
formData.append('style[imageY]', currentImageY || '0');
}
// Add image dimensions
const imageWidth = document.getElementById('bannerImageWidth');
const imageHeight = document.getElementById('bannerImageHeight');
if (imageWidth && imageHeight) {
formData.append('imageWidth', imageWidth.value || '300');
formData.append('imageHeight', imageHeight.value || '200');
formData.append('style[imageWidth]', imageWidth.value || '300');
formData.append('style[imageHeight]', imageHeight.value || '200');
}
// Handle image upload if a new image was selected
const fileInput = document.getElementById('bannerImage');
if (fileInput && fileInput.files && fileInput.files.length > 0) {
formData.append('image', fileInput.files[0]);
} else if (currentImage && currentImage.startsWith('data:image')) {
try {
// If we have a data URL but no file input, convert it to a blob
const response = await fetch(currentImage);
if (!response.ok) throw new Error('Failed to fetch image');
const blob = await response.blob();
formData.append('image', blob, 'banner-image.jpg');
} catch (error) {
console.error('Error processing image:', error);
showNotification('Chyba při zpracování obrázku', 'error');
throw error;
}
}
// Log form data for debugging (without the actual file data)
console.log('Odesílám data:');
for (let [key, value] of formData.entries()) {
console.log(key, value instanceof File ? `[File ${value.name}]` : value);
}
// Prepare headers
const headers = {};
// Add Authorization header if token exists
const token = localStorage.getItem('token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
// Send request with FormData (browser will set correct Content-Type with boundary)
const response = await fetch('/api/banner/update', {
method: 'POST',
headers: headers,
body: formData
});
if (!response.ok) {
const errorText = await response.text().catch(() => 'Neznámá chyba serveru');
console.error('Server error:', errorText);
let errorMessage = 'Chyba při ukládání banneru';
try {
const errorData = JSON.parse(errorText);
errorMessage = errorData.message || errorMessage;
} catch (e) {
errorMessage = errorText || errorMessage;
}
throw new Error(errorMessage);
}
const result = await response.json().catch(() => ({}));
// Show success message
showNotification('Banner byl úspěšně uložen', 'success');
// Update the preview with the new banner data
if (result.imageUrl) {
currentImage = result.imageUrl;
const imagePreview = document.getElementById('imagePreview');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const removeBtn = document.getElementById('removeImageBtn');
if (imagePreview) imagePreview.src = currentImage;
if (imagePreviewContainer) imagePreviewContainer.style.display = 'block';
if (removeBtn) removeBtn.style.display = 'inline-block';
// Update the hidden input if the image was changed
const removeImageInput = document.getElementById('removeImage');
if (removeImageInput) removeImageInput.value = 'false';
// Show templates and size controls since we have an image
const bannerTemplates = document.getElementById('bannerTemplates');
const imageSizeControls = document.getElementById('imageSizeControls');
if (bannerTemplates) bannerTemplates.style.display = 'block';
if (imageSizeControls) imageSizeControls.style.display = 'flex';
}
// Update the preview
updateBannerPreview();
} catch (error) {
console.error('Chyba při ukládání banneru:', error);
showNotification(error.message || 'Nepodařilo se uložit banner', 'error');
} finally {
// Reset button state
if (submitButton) {
submitButton.disabled = false;
submitButton.innerHTML = originalButtonText;
}
isSubmitting = false;
}
}
// Update color previews
function updateColorPreviews() {
const bgColorPreview = document.getElementById('bgColorPreview');
const textColorPreview = document.getElementById('textColorPreview');
const bgColorPicker = document.getElementById('bannerBgColorPicker');
const textColorPicker = document.getElementById('bannerTextColorPicker');
const bgColorInput = document.getElementById('bannerBgColor');
const textColorInput = document.getElementById('bannerTextColor');
if (bgColorPreview && bgColorInput) {
bgColorPreview.style.backgroundColor = bgColorInput.value;
}
if (textColorPreview && textColorInput) {
textColorPreview.style.backgroundColor = textColorInput.value;
}
if (bgColorPicker && bgColorInput) {
bgColorPicker.value = bgColorInput.value;
}
if (textColorPicker && textColorInput) {
textColorPicker.value = textColorInput.value;
}
}
// Remove image
function removeImage() {
const bannerImageInput = document.getElementById('bannerImage');
const bannerImagePreview = document.getElementById('bannerImagePreview');
const removeBtn = document.getElementById('removeImageBtn');
const dragDropMessage = document.querySelector('.drag-drop-message');
const previewContainer = document.getElementById('imagePreviewContainer');
// Reset file input
if (bannerImageInput) {
bannerImageInput.value = '';
}
// Hide image preview
if (bannerImagePreview) {
bannerImagePreview.src = '';
bannerImagePreview.style.display = 'none';
}
// Hide remove button
if (removeBtn) {
removeBtn.style.display = 'none';
}
// Show drag & drop message
if (dragDropMessage) {
dragDropMessage.style.display = 'flex';
}
// Reset preview container
if (previewContainer) {
previewContainer.style.display = 'none';
}
// Clear current image
currentImage = null;
// Update banner preview
updateBannerPreview();
// Show message
showNotification('Obrázek byl odstraněn', 'info');
// Trigger change event on the file input
const bannerImage = document.getElementById('bannerImage');
if (bannerImage) {
const event = new Event('change');
bannerImage.dispatchEvent(event);
}
}
// Update banner preview
function updateBannerPreview() {
const bannerPreview = document.getElementById('bannerPreview');
const bannerPreviewContent = document.getElementById('bannerPreviewContent');
const bannerText = document.getElementById('bannerText')?.value || '';
const bannerVisible = document.getElementById('bannerVisible')?.checked !== false;
const bannerTemplates = document.getElementById('bannerTemplates');
const imagePreview = document.getElementById('imagePreview');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const imageSizeControls = document.getElementById('imageSizeControls');
// Get the current template config or use default
const defaultTemplate = templateConfigs['modern-minimal'] || {};
const template = currentTemplate ? (templateConfigs[currentTemplate] || defaultTemplate) : defaultTemplate;
const fileInput = document.getElementById('bannerImage');
const hasImage = Boolean(currentImage || (fileInput && fileInput.files && fileInput.files.length > 0));
// Ensure template has required properties
if (!template.background && !template.backgroundColor) {
template.backgroundColor = '#f8f9fa';
}
// Show/hide templates and size controls based on image presence
if (bannerTemplates) {
bannerTemplates.style.display = hasImage ? 'block' : 'none';
}
if (imageSizeControls) {
imageSizeControls.style.display = hasImage ? 'flex' : 'none';
}
// Create banner content based on template
let bannerContent = '';
const bannerLink = document.getElementById('bannerLink')?.value || '';
const bannerTextContent = bannerText || 'Náhled banneru';
if (hasImage && currentImage) {
// Get image dimensions and position from inputs or use defaults
const imageWidth = parseInt(document.getElementById('bannerImageWidth')?.value || '300');
const imageHeight = parseInt(document.getElementById('bannerImageHeight')?.value || '200');
const imagePosition = document.getElementById('bannerImagePosition')?.value || 'right';
// Log the current position for debugging
console.log('Image position:', imagePosition);
// Create image container fixed on the right side
const maxWidth = '30%';
const maxHeight = '300px';
// Create flex container for right-aligned image
let imgContainer = `
<div class="banner-image-container" style="
flex: 0 0 auto;
display: flex;
max-width: ${maxWidth};
width: 30%;
justify-content: flex-end;
align-self: flex-start;
">
<div style="
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
">
<img
src="${currentImage}"
style="
max-width: 100%;
max-height: ${maxHeight};
width: auto;
height: auto;
object-fit: contain;
border-radius: 8px;
display: block;
"
alt="Nahraný obrázek"
class="banner-image"
draggable="false"
>
</div>
</div>`;
// Wrap image with link if URL is provided
if (bannerLink) {
imgContainer = `
<a href="${bannerLink}" target="_blank" style="text-decoration: none;">
${imgContainer}
</a>`;
}
// Get all styles from template or use defaults
const styles = {
// Background styles
background: template.background || '',
backgroundColor: (bannerBgColorPicker?.value || template.backgroundColor || '#f8f9fa').trim(),
// Text styles
color: (bannerTextColorPicker?.value || template.textColor || '#212529').trim(),
textColor: (bannerTextColorPicker?.value || template.textColor || '#212529').trim(),
textAlign: (template.textAlign || 'left').trim(),
fontSize: template.fontSize ? `${template.fontSize}px` : '16px',
// Layout styles
padding: (template.padding ? `${template.padding}px` : '20px').trim(),
margin: (template.margin ? `${template.margin}px` : '20px').trim(),
borderRadius: (template.borderRadius ? `${template.borderRadius}px` : '8px').trim(),
// Image position
imagePosition: imagePosition,
// Container styles
containerStyle: (template.containerStyle || '').trim()
};
// Ensure background is properly set
if (!styles.background && styles.backgroundColor) {
styles.background = styles.backgroundColor;
}
// Create text content with template styles - only create the text element once
const textStyle = `
font-size: ${document.getElementById('bannerFontSize')?.value || '16'}px;
color: ${document.getElementById('bannerTextColor')?.value || '#000'};
text-align: ${document.getElementById('bannerTextAlign')?.value || 'left'};
margin: 0;
padding: 10px 0;
line-height: 1.5;
${template?.textStyle || ''}
`;
// Create the text element content
const textContent = `
<div class="banner-text" style="${textStyle}">
${bannerTextContent}
</div>`;
// Set the text element variable that will be used in the banner content
const textElement = textContent;
// Special handling for specific templates
if (currentTemplate === 'dark') {
styles.background = '#2d3748';
styles.backgroundColor = '#2d3748';
styles.color = '#e2e8f0';
styles.textColor = '#e2e8f0';
}
// Build the background style
let backgroundStyle = styles.background.includes('gradient')
? `background: ${styles.background};`
: `background-color: ${styles.backgroundColor};`;
// Create container with right-aligned image layout
bannerContent = `
<div class="banner-content" style="
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: flex-start;
justify-content: space-between;
padding: ${styles.padding};
margin: ${styles.margin};
background: ${styles.background || styles.backgroundColor};
color: ${styles.color};
border-radius: ${styles.borderRadius};
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
${styles.containerStyle};
gap: 20px;
width: 100%;
max-width: 1200px;
box-sizing: border-box;
margin-left: auto;
margin-right: auto;
">
<div style="
flex: 1;
text-align: ${styles.textAlign};
font-size: ${styles.fontSize};
padding-right: 20px;
">
${textElement}
</div>
${imgContainer}
</div>`;
// Show the image preview in the container
try {
const bannerImagePreview = document.getElementById('bannerImagePreview');
if (bannerImagePreview && currentImage) {
bannerImagePreview.src = currentImage;
bannerImagePreview.style.width = '100%';
bannerImagePreview.style.height = 'auto';
bannerImagePreview.style.maxHeight = '200px';
bannerImagePreview.style.display = 'block';
bannerImagePreview.style.objectFit = 'contain';
bannerImagePreview.onerror = function() {
console.error('Failed to load banner image:', this.src);
this.style.display = 'none';
};
}
if (imagePreviewContainer) {
imagePreviewContainer.style.display = 'block';
}
} catch (error) {
console.error('Error updating banner preview:', error);
}
// Add the with-image class to the banner preview for proper spacing
bannerPreview.classList.add('with-image');
} else {
// No image, just show text
bannerContent = `
<div class="banner-content" style="
${template?.containerStyle || ''}
display: flex;
align-items: center;
justify-content: center;
padding: 30px;
min-height: 200px;
">
<div class="banner-text" style="
text-align: center;
color: #666;
font-style: italic;
${template?.textStyle || ''}
">
${bannerText || 'Náhled banneru (pro zobrazení šablon nahrajte obrázek)'}
</div>
</div>`;
// Hide image preview container if no image
if (imagePreviewContainer) {
imagePreviewContainer.style.display = 'none';
}
if (bannerPreview) {
bannerPreview.classList.remove('with-image');
}
}
// Apply template styles to the banner preview
if (template && bannerPreview) {
Object.assign(bannerPreview.style, {
backgroundColor: template.bgColor || '#f8f9fa',
color: template.textColor || '#212529',
textAlign: template.textAlign || 'left',
fontSize: `${template.fontSize || 16}px`,
padding: `${template.padding || 20}px`,
margin: `${template.margin || 20}px 0`,
borderRadius: `${template.borderRadius || 8}px`,
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
display: 'block',
overflow: 'hidden',
width: '100%',
maxWidth: '1200px',
transition: 'all 0.3s ease',
position: 'relative'
});
}
// Update the banner content with the generated HTML
if (bannerPreviewContent) {
bannerPreviewContent.innerHTML = bannerContent;
}
// Add event listener for visibility toggle
document.addEventListener('DOMContentLoaded', function() {
const visibilityToggle = document.getElementById('bannerVisibility');
const hiddenInput = document.getElementById('bannerVisible');
if (visibilityToggle && hiddenInput) {
// Initialize visibility state
hiddenInput.value = visibilityToggle.checked ? 'true' : 'false';
// Add change event listener
visibilityToggle.addEventListener('change', function() {
hiddenInput.value = this.checked ? 'true' : 'false';
updateBannerPreview();
});
}
// Also update the banner preview when the page loads
updateBannerPreview();
});
// Add event listeners for width/height changes
const imageWidthInput = document.getElementById('bannerImageWidth');
const imageHeightInput = document.getElementById('bannerImageHeight');
if (imageWidthInput) {
imageWidthInput.value = template.imageWidth || 300;
imageWidthInput.addEventListener('input', updateBannerPreview);
}
if (imageHeightInput) {
imageHeightInput.value = template.imageHeight || 200;
imageHeightInput.addEventListener('input', updateBannerPreview);
}
// Make sure the preview is visible
if (bannerPreview) {
bannerPreview.style.visibility = 'visible';
}
// Update image position and visibility
const bannerImgElement = document.getElementById('bannerImagePreview');
const hasImageElement = bannerImgElement && !bannerImgElement.classList.contains('d-none') && bannerImgElement.src;
if (hasImageElement) {
const bannerLinkValue = document.getElementById('bannerLink')?.value || '';
if (bannerLinkValue) {
bannerImgElement.style.cursor = 'pointer';
bannerImgElement.onclick = (e) => {
e.preventDefault();
window.open(bannerLinkValue, '_blank');
};
} else {
bannerImgElement.style.cursor = 'default';
bannerImgElement.onclick = null;
}
}
}
// Apply preset
function applyPreset(preset) {
const style = presets[preset];
if (!style) return;
bannerBgColor.value = style.backgroundColor;
bannerTextColor.value = style.textColor;
bannerTextAlign.value = style.textAlign;
// Update color pickers to match input fields
bannerBgColorPicker.value = style.backgroundColor;
bannerTextColorPicker.value = style.textColor;
updateColorPreviews();
updateBannerPreview();
}
// Event Listeners
// Debounced update for text inputs
const debouncedUpdatePreview = debounce(() => {
updateColorPreviews();
updateBannerPreview();
}, 300);
// This function will be called after DOM is loaded
function setupColorInputListeners() {
if (bannerBgColor) {
bannerBgColor.addEventListener('input', () => {
// Update color preview immediately
if (bgColorPreview) {
bgColorPreview.style.backgroundColor = bannerBgColor.value;
}
// Debounce the full preview update
debouncedUpdatePreview();
});
}
if (bannerTextColor) {
bannerTextColor.addEventListener('input', () => {
// Update color preview immediately
if (textColorPreview) {
textColorPreview.style.backgroundColor = bannerTextColor.value;
}
// Debounce the full preview update
debouncedUpdatePreview();
});
}
}
// Connect color pickers to input fields
const bannerBgColorPicker = document.getElementById('bannerBgColorPicker');
const bannerTextColorPicker = document.getElementById('bannerTextColorPicker');
// Debounce function to improve performance
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
// These event listeners will be set up after the DOM is fully loaded
// Setup draggable image functionality
function setupDraggableImage() {
const draggableImage = document.querySelector('.draggable-image');
if (!draggableImage) return;
// Remove any existing event listeners to prevent duplicates
const newDraggable = draggableImage.cloneNode(true);
draggableImage.parentNode.replaceChild(newDraggable, draggableImage);
let isDragging = false;
let startX, startY;
let originalX = parseInt(currentImageX) || 0;
let originalY = parseInt(currentImageY) || 0;
// Mouse events for desktop
newDraggable.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', endDrag);
// Touch events for mobile
newDraggable.addEventListener('touchstart', startDragTouch);
document.addEventListener('touchmove', dragTouch);
document.addEventListener('touchend', endDrag);
function startDrag(e) {
e.preventDefault();
isDragging = true;
startX = e.clientX;
startY = e.clientY;
newDraggable.classList.add('dragging');
console.log('Started dragging at', startX, startY);
}
function startDragTouch(e) {
if (e.touches.length === 1) {
isDragging = true;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
newDraggable.classList.add('dragging');
}
}
function drag(e) {
if (!isDragging) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
const newX = originalX + deltaX;
const newY = originalY + deltaY;
newDraggable.style.left = `${newX}px`;
newDraggable.style.top = `${newY}px`;
// Update current position values
currentImageX = newX.toString();
currentImageY = newY.toString();
console.log('Dragging to', newX, newY);
}
function dragTouch(e) {
if (!isDragging || e.touches.length !== 1) return;
const deltaX = e.touches[0].clientX - startX;
const deltaY = e.touches[0].clientY - startY;
const newX = originalX + deltaX;
const newY = originalY + deltaY;
newDraggable.style.left = `${newX}px`;
newDraggable.style.top = `${newY}px`;
// Update current position values
currentImageX = newX.toString();
currentImageY = newY.toString();
}
function endDrag() {
if (!isDragging) return;
isDragging = false;
originalX = parseInt(currentImageX);
originalY = parseInt(currentImageY);
newDraggable.classList.remove('dragging');
console.log('Finished dragging at', originalX, originalY);
}
}
const templateConfigs = {
'default': {
name: 'Výchozí',
containerStyle: 'background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);',
textStyle: 'color: #2d3748; font-weight: 500;',
buttonStyle: 'background: #4a6cf7; color: white; border: none; padding: 8px 16px; border-radius: 4px;',
isVisible: true
},
'modern': {
name: 'Moderní',
containerStyle: 'background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);',
textStyle: 'color: #1a365d; font-weight: 600; text-shadow: 0 1px 2px rgba(0,0,0,0.1);',
buttonStyle: 'background: #2b6cb0; color: white; border: 2px solid #2c5282; padding: 8px 20px; border-radius: 25px;',
isVisible: true
},
'elegant': {
name: 'Elegantní',
containerStyle: 'background: linear-gradient(to right, #f5f7fa 0%, #e4e8f0 100%); border-left: 4px solid #4a5568;',
textStyle: 'color: #2d3748; font-family: Georgia, serif;',
buttonStyle: 'background: #4a5568; color: white; border: none; padding: 8px 16px; border-radius: 2px; letter-spacing: 1px;',
isVisible: true
},
'alert': {
name: 'Upozornění',
containerStyle: 'background: #fff3cd; border: 1px solid #ffeeba;',
textStyle: 'color: #856404;',
buttonStyle: 'background: #ffc107; color: #856404; border: 1px solid #d39e00; padding: 8px 16px; border-radius: 4px;',
isVisible: true
},
'dark': {
name: 'Tmavý motiv',
containerStyle: 'background: #2d3748;',
textStyle: 'color: #e2e8f0;',
buttonStyle: 'background: #4fd1c5; color: #1a202c; font-weight: 600; padding: 8px 16px; border-radius: 4px;',
isVisible: true
},
'gradient': {
name: 'Přechod',
containerStyle: 'background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);',
textStyle: 'color: white; text-shadow: 0 1px 3px rgba(0,0,0,0.2);',
buttonStyle: 'background: white; color: #4a6cf7; border: none; padding: 8px 20px; border-radius: 4px; font-weight: 600;',
isVisible: true
},
'gradient-blue': {
name: 'Modrý gradient',
containerStyle: 'background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); color: white;',
background: 'linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%)',
backgroundColor: '#4f46e5',
textStyle: 'color: white; font-family: \'Inter\', sans-serif; font-size: 16px; font-weight: 500;',
buttonStyle: 'background-color: white; color: #4f46e5; border: none; padding: 10px 24px; border-radius: 6px; font-weight: 600;',
isVisible: true
}
};
// Setup template selection and generate template previews
document.addEventListener('DOMContentLoaded', () => {
const templateGrid = document.getElementById('templateGrid');
// Get references to form elements
const bannerBgColorPicker = document.getElementById('bannerBgColorPicker');
const bannerBgColor = document.getElementById('bannerBgColor');
const bannerTextColorPicker = document.getElementById('bannerTextColorPicker');
const bannerTextColor = document.getElementById('bannerTextColor');
const bannerText = document.getElementById('bannerText');
const bannerLink = document.getElementById('bannerLink');
const bannerTextAlign = document.getElementById('bannerTextAlign');
const bannerFontSize = document.getElementById('bannerFontSize');
const bannerPadding = document.getElementById('bannerPadding');
const bannerMargin = document.getElementById('bannerMargin');
const bannerBorderRadius = document.getElementById('bannerBorderRadius');
const bannerVisibility = document.getElementById('bannerVisibility');
const stylePresets = document.querySelectorAll('.style-preset');
const saveBannerBtn = document.getElementById('saveBannerBtn');
// Set up color picker event listeners if elements exist
if (bannerBgColorPicker && bannerBgColor) {
bannerBgColorPicker.addEventListener('input', () => {
bannerBgColor.value = bannerBgColorPicker.value;
updateColorPreviews();
});
}
if (bannerTextColorPicker && bannerTextColor) {
bannerTextColorPicker.addEventListener('input', () => {
bannerTextColor.value = bannerTextColorPicker.value;
updateColorPreviews();
});
}
// For other form controls, use debounced updates on input
const formControls = [
bannerText, bannerLink, bannerTextAlign, bannerFontSize,
bannerPadding, bannerMargin, bannerBorderRadius, bannerVisibility
];
formControls.forEach(control => {
if (control) {
control.addEventListener('input', debounce(() => {
updateBannerPreview();
}, 300));
}
});
// Toggle visibility
if (bannerVisibility) {
bannerVisibility.addEventListener('change', () => {
updateBannerPreview();
});
}
// Style presets
if (stylePresets.length > 0) {
stylePresets.forEach(preset => {
preset.addEventListener('click', () => applyPreset(preset.dataset.preset));
});
}
// Save button
if (saveBannerBtn) {
saveBannerBtn.addEventListener('click', saveBanner);
}
// Generate template previews
if (templateGrid) {
// Clear existing content
templateGrid.innerHTML = '';
// Create a preview for each template
Object.entries(templateConfigs).forEach(([id, template]) => {
// Create template card container
const templateElement = document.createElement('div');
templateElement.className = 'group relative cursor-pointer rounded-lg overflow-hidden border border-gray-200 hover:border-blue-500 transition-all duration-200 hover:shadow-lg';
templateElement.dataset.templateId = id;
// Create preview container with aspect ratio 16:9
templateElement.innerHTML = `
<div class="relative pt-[56.25%] bg-white">
<div class="absolute inset-0 flex items-center justify-center p-4 text-center" style="${template.containerStyle}">
<div class="w-full h-full relative">
${template.imageStyle ?
`<div class="absolute inset-0" style="${template.imageStyle}">
<div class="w-full h-full bg-gray-200"></div>
</div>` :
''
}
<div class="absolute inset-0 flex items-center justify-center p-4">
<div class="text-center px-4 py-2 rounded-md bg-black bg-opacity-50 text-white" style="${template.textStyle}">
${template.name}
</div>
</div>
</div>
</div>
</div>
<div class="p-3 bg-white border-t border-gray-100">
<div class="flex justify-between items-center">
<span class="text-sm font-medium text-gray-900 truncate">${template.name}</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
Vybrat
</span>
</div>
</div>
<div class="absolute inset-0 bg-blue-500 bg-opacity-0 group-hover:bg-opacity-10 transition-all duration-200"></div>
`;
// Add click event to apply template
templateElement.addEventListener('click', () => {
// Remove active class from all templates
document.querySelectorAll('[data-template-id]').forEach(el => {
el.classList.remove('ring-2', 'ring-blue-500');
});
// Add active class to selected template
templateElement.classList.add('ring-2', 'ring-blue-500');
applyTemplate(id);
});
// Add template to grid
templateGrid.appendChild(templateElement);
});
}
// Setup event listeners
function setupEventListeners() {
// Banner form submission
const bannerForm = document.getElementById('bannerForm');
if (bannerForm) {
bannerForm.addEventListener('submit', saveBanner);
}
// Image upload
const bannerImage = document.getElementById('bannerImage');
if (bannerImage) {
bannerImage.addEventListener('change', handleImageUpload);
}
// Remove image button
const removeImageBtn = document.getElementById('removeImageBtn');
if (removeImageBtn) {
removeImageBtn.addEventListener('click', removeImage);
}
// Template selection
const templateItems = document.querySelectorAll('.template-item');
templateItems.forEach(item => {
item.addEventListener('click', function() {
const templateId = this.dataset.template;
if (templateId) {
applyTemplate(templateId);
}
});
});
// Image size controls
const sizeInputs = document.querySelectorAll('#bannerImageWidth, #bannerImageHeight');
sizeInputs.forEach(input => {
input.addEventListener('input', updateBannerPreview);
});
// Preview updates for text content
const previewInputs = [
document.getElementById('bannerText'),
document.getElementById('bannerLink'),
document.getElementById('bannerVisible')
];
previewInputs.forEach(input => {
if (input) {
input.addEventListener('input', updateBannerPreview);
input.addEventListener('change', updateBannerPreview);
}
});
// Checkbox for banner visibility
const bannerVisible = document.getElementById('bannerVisible');
if (bannerVisible) {
bannerVisible.addEventListener('change', updateBannerPreview);
}
// Initialize with default template
const defaultTemplate = document.querySelector(`[data-template="${currentTemplate}"]`);
if (defaultTemplate) {
defaultTemplate.classList.add('active');
}
// Initial preview update
updateBannerPreview();
// Show/hide templates and size controls based on image presence
const bannerTemplates = document.getElementById('bannerTemplates');
const imageSizeControls = document.getElementById('imageSizeControls');
const hasImage = currentImage || (bannerImage && bannerImage.files.length > 0);
if (bannerTemplates) {
bannerTemplates.style.display = hasImage ? 'block' : 'none';
}
if (imageSizeControls) {
imageSizeControls.style.display = hasImage ? 'flex' : 'none';
}
}
setupEventListeners();
// Show templates when image is uploaded
const bannerImage = document.getElementById('bannerImage');
if (bannerImage) {
bannerImage.addEventListener('change', () => {
const templates = document.getElementById('bannerTemplates');
if (templates) {
templates.style.display = 'block';
}
});
}
loadBanner();
});
// Apply selected template and update form fields
function applyTemplate(templateId) {
const template = templateConfigs[templateId];
if (!template) return;
// Store the selected template
currentTemplate = templateId;
// Update form fields with template styles
if (template.containerStyle) {
const bannerContainer = document.getElementById('bannerPreview');
if (bannerContainer) {
bannerContainer.style = template.containerStyle;
}
}
// Update text styles if they exist in the template
if (template.textStyle) {
const bannerText = document.getElementById('bannerText');
if (bannerText) {
bannerText.style = template.textStyle;
}
}
// Update button styles if they exist in the template
if (template.buttonStyle) {
const bannerButton = document.querySelector('.banner-button');
if (bannerButton) {
bannerButton.style = template.buttonStyle;
}
}
// Update the banner preview with the new template
updateBannerPreview();
// Show success message
showNotification(`Šablona "${template.name}" byla použita`, 'success');
// Scroll to preview to show the changes
const bannerPreview = document.getElementById('bannerPreview');
if (bannerPreview) {
bannerPreview.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
</script>
</body>
</html>