Files
PPve/index.html
T
Tomas Dvorak 63945266f1 ts
2025-06-11 20:39:05 +02:00

603 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aplikační Rozcestník</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<style>
.card {
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
</style>
<script>
// Function to get icon based on app description or name
function getAppIcon(app) {
// If icon (image) is provided, use it
if (app.icon) {
return `<img src="/uploads/${app.icon}" alt="${app.name}" class="w-12 h-12 object-contain">`;
}
// If iconClass is provided, use it for Font Awesome icons
const iconToUse = app.iconClass || (() => {
// Otherwise, determine icon based on description or name
const lowerName = app.name.toLowerCase();
const lowerDesc = (app.description || '').toLowerCase();
if (lowerName.includes('auto') || lowerName.includes('auto') || lowerDesc.includes('auto') || lowerDesc.includes('vůz')) {
return 'fa-car-side';
} else if (lowerName.includes('jídlo') || lowerName.includes('oběd') || lowerDesc.includes('jídlo') || lowerDesc.includes('oběd')) {
return 'fa-utensils';
} else if (lowerName.includes('podpora') || lowerName.includes('helpdesk') || lowerDesc.includes('podpora') || lowerDesc.includes('helpdesk')) {
return 'fa-headset';
} else if (lowerName.includes('úkol') || lowerName.includes('task') || lowerDesc.includes('úkol') || lowerDesc.includes('task')) {
return 'fa-tasks';
} else if (lowerName.includes('kontakt') || lowerName.includes('kontakty') || lowerDesc.includes('kontakt') || lowerDesc.includes('kontakty')) {
return 'fa-address-book';
} else {
// Default icon
return 'fa-globe';
}
})();
// Return the Font Awesome icon with appropriate classes
return `<i class="fas ${iconToUse} text-2xl"></i>`;
}
// Function to get color class based on app name or description
function getAppColorClass(app) {
const lowerName = app.name.toLowerCase();
const lowerDesc = (app.description || '').toLowerCase();
if (lowerName.includes('auto') || lowerName.includes('auto') || lowerDesc.includes('auto') || lowerDesc.includes('vůz')) {
return 'border-blue-600 bg-blue-100 text-blue-600';
} else if (lowerName.includes('jídlo') || lowerName.includes('oběd') || lowerDesc.includes('jídlo') || lowerDesc.includes('oběd')) {
return 'border-green-600 bg-green-100 text-green-600';
} else if (lowerName.includes('podpora') || lowerName.includes('helpdesk') || lowerDesc.includes('podpora') || lowerDesc.includes('helpdesk')) {
return 'border-orange-600 bg-orange-100 text-orange-600';
} else if (lowerName.includes('úkol') || lowerName.includes('task') || lowerDesc.includes('úkol') || lowerDesc.includes('task')) {
return 'border-purple-600 bg-purple-100 text-purple-600';
} else if (lowerName.includes('kontakt') || lowerName.includes('kontakty') || lowerDesc.includes('kontakt') || lowerDesc.includes('kontakty')) {
return 'border-blue-500 bg-blue-100 text-blue-500';
} else {
// Default color
return 'border-gray-600 bg-gray-100 text-gray-600';
}
}
// Function to create app card HTML
function createAppCard(app) {
const colorClass = getAppColorClass(app);
const iconHtml = getAppIcon(app);
const isExternal = app.url.startsWith('http');
const target = isExternal ? '_blank' : '_self';
const [borderClass, bgClass, textClass] = colorClass.split(' ');
return `
<div class="card bg-white rounded-xl shadow p-6 border-t-4 ${borderClass}" data-name="${app.name.toLowerCase()} ${app.description ? app.description.toLowerCase() : ''}">
<div class="rounded-full w-14 h-14 flex items-center justify-center ${bgClass} ${textClass}">
${iconHtml}
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2 mt-4">${app.name}</h2>
<p class="text-gray-600 mb-4">${app.description || 'Firemní aplikace'}</p>
<a href="${app.url}" target="${target}" class="block text-center ${bgClass} hover:${bgClass.replace('bg-', 'bg-').replace('100', '200')} font-medium py-2 px-4 rounded-lg transition-colors">
Otevřít aplikaci
</a>
</div>
`;
}
// Function to load and display dynamic apps
async function loadApps() {
const loadingIndicator = document.getElementById('loadingIndicator');
const appsGrid = document.getElementById('appsGrid');
try {
// Show loading indicator
if (loadingIndicator) loadingIndicator.style.display = 'block';
const response = await fetch('/api/apps');
if (!response.ok) throw new Error('Failed to load apps');
const apps = await response.json();
// Remove any existing dynamic apps (keep hardcoded ones)
const dynamicApps = Array.from(appsGrid.querySelectorAll('[data-id^="dynamic-"]'));
dynamicApps.forEach(app => app.remove());
if (apps.length > 0) {
// Add each dynamic app to the grid
apps.forEach(app => {
// Skip if this is a hardcoded app that's already displayed
const hardcodedIds = ['hardcoded-car', 'hardcoded-lunch', 'hardcoded-osticket', 'hardcoded-kanboard'];
if (hardcodedIds.includes(app.id)) return;
const appCard = document.createElement('div');
appCard.setAttribute('data-id', 'dynamic-' + app.id);
appCard.innerHTML = createAppCard(app);
appsGrid.appendChild(appCard.firstElementChild);
});
}
// Initialize search functionality
initializeSearch();
} catch (error) {
console.error('Error loading apps:', error);
const errorDiv = document.createElement('div');
errorDiv.className = 'col-span-full text-center py-8';
errorDiv.innerHTML = `
<i class="fas fa-exclamation-triangle text-4xl text-red-500 mb-2"></i>
<p class="text-red-600">Nepodařilo se načíst další aplikace</p>
<p class="text-sm text-gray-500 mt-2">${error.message}</p>
`;
appsGrid.appendChild(errorDiv);
} finally {
// Hide loading indicator
if (loadingIndicator) loadingIndicator.style.display = 'none';
}
}
// Function to initialize search functionality
function initializeSearch() {
const searchInput = document.getElementById('search');
if (!searchInput) return;
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
const appCards = document.querySelectorAll('.card');
appCards.forEach(card => {
const cardText = card.getAttribute('data-name');
if (cardText.includes(searchTerm)) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
});
}
// Load and display banner
async function loadBanner() {
const bannerContainer = document.getElementById('bannerContainer');
if (!bannerContainer) {
console.error('Banner container not found');
return;
}
try {
const response = await fetch('/api/banner');
if (!response.ok) {
console.error('Failed to load banner:', response.status);
bannerContainer.style.display = 'none';
return;
}
const banner = await response.json();
console.log('Banner data:', JSON.stringify(banner, null, 2));
// Check if banner should be visible
const isVisible = (() => {
const visibility = banner.Style?.IsVisible ?? banner.Style?.isVisible ??
banner.IsVisible ?? banner.isVisible ?? banner.visible;
if (typeof visibility === 'string') {
return visibility.toLowerCase() === 'true';
}
return Boolean(visibility !== false);
})();
if (!isVisible) {
console.log('Banner is not visible');
bannerContainer.style.display = 'none';
return;
}
// Clear and prepare container
bannerContainer.innerHTML = '';
bannerContainer.style.display = 'block';
// Create banner content container
const bannerContentEl = document.createElement('div');
bannerContentEl.id = 'bannerContent';
bannerContainer.appendChild(bannerContentEl);
// Get banner content and style
const bannerText = banner.Text || banner.text || '';
const bannerImage = banner.Image || banner.image || '';
const bannerLink = banner.Link || banner.link || '';
const bannerStyle = banner.Style || {};
const imagePosition = bannerStyle.ImagePosition || 'right';
// Process HTML content - unescape HTML entities
const processHtml = (html) => {
const textarea = document.createElement('textarea');
textarea.innerHTML = html;
return textarea.value;
};
// Create main container
const container = document.createElement('div');
container.className = 'banner-container';
container.style.cssText = `
background: ${bannerStyle.BackgroundColor || '#f8f9fa'};
color: ${bannerStyle.TextColor || '#212529'};
border-radius: ${bannerStyle.BorderRadius || '8px'};
padding: ${bannerStyle.Padding || '20px'};
margin: ${bannerStyle.Margin || '20px 0'};
position: relative;
overflow: hidden;
width: 100%;
box-sizing: border-box;
`;
// Create content wrapper
const wrapper = document.createElement('div');
wrapper.className = 'banner-wrapper';
wrapper.style.cssText = `
display: flex;
flex-direction: ${imagePosition === 'left' ? 'row' : 'row-reverse'};
align-items: center;
gap: 20px;
width: 100%;
`;
// Create text content
if (bannerText) {
const textContent = document.createElement('div');
textContent.className = 'banner-text';
textContent.style.cssText = `
flex: 1;
color: ${bannerStyle.TextColor || '#212529'};
font-size: ${bannerStyle.FontSize || '16px'};
text-align: ${bannerStyle.TextAlign || 'left'};
line-height: 1.5;
`;
textContent.innerHTML = processHtml(bannerText);
wrapper.appendChild(textContent);
}
// Create image content if exists
if (bannerImage) {
const imageContainer = document.createElement('div');
imageContainer.className = 'banner-image';
imageContainer.style.cssText = `
flex: 0 0 40%;
max-width: 40%;
position: relative;
`;
const img = document.createElement('img');
// Handle image path - use as is if it's an absolute path or URL
img.src = bannerImage.startsWith('http') || bannerImage.startsWith('/')
? bannerImage
: `/uploads/${bannerImage.replace(/^\/+/, '')}`;
img.alt = 'Banner image';
img.style.cssText = `
max-width: 100%;
max-height: 200px;
width: auto;
height: auto;
object-fit: contain;
border-radius: 4px;
display: block;
`;
imageContainer.appendChild(img);
wrapper.appendChild(imageContainer);
}
// Add wrapper to container
container.appendChild(wrapper);
// Add link to banner if it exists
if (bannerLink) {
const link = document.createElement('a');
link.href = bannerLink;
link.target = '_blank';
link.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
text-decoration: none;
color: inherit;
`;
container.insertBefore(link, container.firstChild);
}
// Add container to banner content
bannerContentEl.appendChild(container);
} catch (error) {
console.error('Error loading banner:', error);
bannerContainer.style.display = 'none';
}
}
// Load banner when page loads
document.addEventListener('DOMContentLoaded', () => {
// Move banner to the correct position before loading
const bannerContainer = document.getElementById('bannerContainer');
const main = document.querySelector('main');
if (bannerContainer && main) {
main.insertBefore(bannerContainer, main.firstElementChild);
}
// Load banner content
loadBanner();
// Load apps
loadApps();
});
tailwind.config = {
theme: {
extend: {
colors: {
'brand-blue': '#004990',
'brand-light-blue': '#0072b0',
'brand-gray': '#f0f2f5'
}
}
}
}
</script>
</head>
<body class="bg-gray-100 min-h-screen">
<!-- Banner Container - Will be populated by JavaScript -->
<div id="bannerContainer" style="display: none; width: 100%; margin: 0 auto; max-width: 1200px; padding: 0 1rem;">
<div id="bannerContent"></div>
</div>
<nav class="bg-brand-blue text-white shadow-lg">
<div class="max-w-6xl mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<a href="http://webportal/index.html"><img src="http://pp-kunovice.cz/wp-content/uploads/2022/04/logo-retina-white.png" alt="Poppe Potthoff Logo" class="h-10"></a>
<div class="hidden md:block text-xl font-semibold">Poppe + Potthoff</div>
</div>
<!-- Mobile menu button -->
<div class="md:hidden">
<button id="mobile-menu-button" class="text-white focus:outline-none">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<!-- Desktop menu -->
<div class="hidden md:flex space-x-6">
<a href="http://webportal/index.html" class="hover:text-brand-light-blue">Rozcestník</a>
<a href="http://webportal/evidence-aut" class="hover:text-brand-light-blue">Evidence aut</a>
<a href="http://ppc-app/pwkweb2/" class="hover:text-brand-light-blue">Obědy</a>
<a href="http://osticket/" class="hover:text-brand-light-blue">OSticket</a>
<a href="http://kanboard/" class="hover:text-brand-light-blue">Kanboard</a>
<a href="http://webportal:8080" class="hover:text-brand-light-blue">Kontakt</a>
<a href="/rezervace-aut" class="hover:text-brand-light-blue">Rezervace aut</a>
</div>
</div>
<!-- Mobile menu -->
<div id="mobile-menu" class="hidden md:hidden px-2 pt-2 pb-3 space-y-1">
<a href="webportal/index.html" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Rozcestník</a>
<a href="webportal/evidence-aut" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Evidence aut</a>
<a href="http://ppc-app/pwkweb2/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Obědy</a>
<a href="http://osticket/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">OSticket</a>
<a href="http://kanboard/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kanboard</a>
<a href="http://webportal:8080" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kontakt</a>
<a href="/rezervace-aut" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Rezervace aut</a>
</div>
</nav>
<script>
// Mobile menu toggle
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
mobileMenuButton.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
</script>
<!-- Page Header -->
<div class="bg-gradient-to-r from-brand-blue to-brand-light-blue text-white py-6 mb-8">
<div class="max-w-6xl mx-auto px-4">
<h1 class="text-3xl font-bold">Poppe + Potthoff - Firemní Aplikace</h1>
<p class="text-gray-100 mt-2">Rychlý přístup ke všem důležitým systémům</p>
</div>
</div>
<main class="container mx-auto px-4 py-4">
<!-- Banner will be inserted here by JavaScript -->
<!-- Search -->
<div class="mb-8 max-w-xl mx-auto">
<div class="relative">
<input type="text" id="search" placeholder="Hledat aplikaci..." style="margin-bottom: 20px;"
class="w-full px-4 py-3 rounded-lg shadow-sm border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none">
<div class="absolute right-3 top-3 text-gray-400">
<i class="fas fa-search"></i>
</div>
</div>
</div>
<!-- Apps Grid -->
<div id="appsGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Add Rezervace aut card before other hardcoded apps -->
<div class="card bg-white rounded-xl shadow p-6 border-t-4 border-indigo-600" data-name="rezervace aut vozidel kalendar" data-id="hardcoded-rezervace">
<div class="rounded-full w-14 h-14 flex items-center justify-center bg-indigo-100 text-indigo-600 mb-4">
<i class="fas fa-calendar-alt text-2xl"></i>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">Rezervace vozidel</h2>
<p class="text-gray-600 mb-4">Kalendář pro rezervaci služebních vozidel a plánování jízd</p>
<a href="/rezervace-aut" class="block text-center bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Otevřít aplikaci
</a>
</div>
<!-- Other hardcoded apps -->
<div class="card bg-white rounded-xl shadow p-6 border-t-4 border-blue-600" data-name="zápis cest aut project" data-id="hardcoded-car">
<div class="rounded-full w-14 h-14 flex items-center justify-center bg-blue-100 text-blue-600 mb-4">
<i class="fas fa-car-side text-2xl"></i>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">Záznam služebních jízd</h2>
<p class="text-gray-600 mb-4">Jednoduchý systém pro evidenci a správu jízd služebními vozidly.</p>
<a href="/evidence-aut" class="block text-center bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Otevřít aplikaci
</a>
</div>
<div class="card bg-white rounded-xl shadow p-6 border-t-4 border-green-600" data-name="obědy obedy jídlo lunch" data-id="hardcoded-lunch">
<div class="rounded-full w-14 h-14 flex items-center justify-center bg-green-100 text-green-600 mb-4">
<i class="fas fa-utensils text-2xl"></i>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">Objednávka obědů</h2>
<p class="text-gray-600 mb-4">Portál pro objednávku a přehled firemních obědů</p>
<a href="http://ppc-app/pwkweb2/" class="block text-center bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Otevřít aplikaci
</a>
</div>
<div class="card bg-white rounded-xl shadow p-6 border-t-4 border-orange-600" data-name="osticket pomoc podpora support ticket" data-id="hardcoded-osticket">
<div class="rounded-full w-14 h-14 flex items-center justify-center bg-orange-100 text-orange-600 mb-4">
<i class="fas fa-headset text-2xl"></i>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">OSTicket</h2>
<p class="text-gray-600 mb-4">Systém technické podpory a hlášení problémů</p>
<a href="http://osticket/" class="block text-center bg-orange-600 hover:bg-orange-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Otevřít aplikaci
</a>
</div>
<div class="card bg-white rounded-xl shadow p-6 border-t-4 border-purple-600" data-name="kanboard úkoly úkolníček tasks" data-id="hardcoded-kanboard">
<div class="rounded-full w-14 h-14 flex items-center justify-center bg-purple-100 text-purple-600 mb-4">
<i class="fas fa-tasks text-2xl"></i>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">Kanboard</h2>
<p class="text-gray-600 mb-4">Správa úkolů a projektů v přehledném kanban stylu</p>
<a href="http://kanboard/" class="block text-center bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Otevřít aplikaci
</a>
</div>
<div class="card bg-white rounded-xl shadow p-6 border-t-4 border-indigo-600" data-name="rezervace aut vozidel calendar">
<div class="rounded-full w-14 h-14 flex items-center justify-center bg-indigo-100 text-indigo-600 mb-4">
<i class="fas fa-calendar-alt text-2xl"></i>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">Rezervace služebních vozů</h2>
<p class="text-gray-600 mb-4">Kalendář pro rezervaci a plánování služebních jízd</p>
<a href="/rezervace-aut" class="block text-center bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Otevřít aplikaci
</a>
</div>
<!-- Dynamic apps will be loaded here -->
</div>
<!-- Loading indicator for dynamic apps -->
<div id="loadingIndicator" class="text-center py-8 hidden">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-600"></div>
<p class="mt-2 text-gray-600">Načítám další aplikace...</p>
</div>
<style>
/* Rounded border */
hr.rounded {
border-top: 2px solid #bbb;
border-radius: 5px;
margin-top: 20px;
margin-bottom: 20px;
}
</style>
<hr class="rounded">
<!-- Contact card - Subtle and full width -->
<div>
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="rounded-full w-10 h-10 flex items-center justify-center bg-gray-100 text-gray-500 mr-3">
<i class="fas fa-phone-alt"></i>
</div>
<div>
<h3 class="text-sm font-medium text-gray-700">Telefonní seznam</h3>
<p class="text-xs text-gray-500">Firemní kontakty a důležitá čísla</p>
</div>
</div>
<a href="http://webportal:8080" class="text-sm text-blue-600 hover:text-blue-800 hover:underline">
Otevřít
</a>
</div>
</div>
</div>
</main>
<footer class="bg-gray-800 text-gray-400 py-8 mt-12">
<div class="max-w-6xl mx-auto px-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Company Info -->
<div>
<h3 class="text-white text-lg font-semibold mb-4">Poppe + Potthoff CZ</h3>
<p class="mb-2">IČO: 26902214</p>
<p class="mb-2">DIČ: CZ26902214</p>
<p class="mb-2">Schránka: gfrk5qy</p>
<p>Na Záhonech 1086, 686 04 Kunovice</p>
</div>
<!-- Quick Links -->
<div>
<h3 class="text-white text-lg font-semibold mb-4">Rychlé odkazy</h3>
<ul class="space-y-2">
<li><a href="http://webportal/" class="hover:text-white">Rozcestník</a></li>
<li><a href="http://webportal/evidence-aut" class="hover:text-white">Evidence aut</a></li>
<li><a href="http://ppc-app/pwkweb2/" class="hover:text-white">Objednávka obědů</a></li>
<li><a href="http://osticket/" class="hover:text-white">Technická podpora</a></li>
<li><a href="http://webportal:8080" class="hover:text-white">Kontakty</a></li>
</ul>
</div>
<!-- Copyright -->
<div class="md:text-right">
<img src="http://pp-kunovice.cz/wp-content/uploads/2022/04/logo-retina-white.png" alt="Poppe Potthoff Logo" class="h-10 mb-4 inline-block">
<p class="text-sm"> 2025 Poppe + Potthoff CZ</p>
<p class="text-xs mt-2">Všechna práva vyhrazena</p>
</div>
</div>
<div class="border-t border-gray-700 mt-8 pt-6 text-center text-sm">
<p>Created by <a href="https://tdvorak.dev" class="text-blue-400 hover:text-blue-300">TDvorak</a></p>
</div>
</div>
</footer>
<script>
// Search functionality
const searchInput = document.getElementById('search');
const appCards = document.querySelectorAll('.card');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
appCards.forEach(card => {
const cardName = card.getAttribute('data-name').toLowerCase();
const cardTitle = card.querySelector('h2').textContent.toLowerCase();
const cardDesc = card.querySelector('p').textContent.toLowerCase();
if (cardName.includes(searchTerm) || cardTitle.includes(searchTerm) || cardDesc.includes(searchTerm)) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
});
</script>
</body>
</html>