GestionTablets/templates/base.html

652 lines
26 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="{{ language or 'en' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ _('Tablet Management System') }}</title>
<!-- Tailwind CSS via CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-1y8c6Y8XkZ7Q1Y+v5vYpX+g5Z4XhY+6eZ6X9p6Z1cM5Xf+Z7x2J6FQ0w9Xc3YfXMgZ6e5Z7+6f5Q==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Custom styles -->
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
}
/* HTMX loading spinner */
.htmx-indicator {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(0,0,0,.3);
border-radius: 50%;
border-top-color: #FC6A03;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Hide HTMX indicator by default */
.htmx-indicator {
opacity: 0;
transition: opacity 200ms ease-in;
}
.htmx-request .htmx-indicator {
opacity: 1;
}
/* Dark mode styles */
body.dark-mode {
background-color: #1a1a2e;
color: #e0e0e0;
}
body.dark-mode .bg-gray-50 {
background-color: #1a1a2e;
}
body.dark-mode .bg-white {
background-color: #16213e;
}
body.dark-mode .text-gray-900,
body.dark-mode .text-gray-800,
body.dark-mode .text-gray-700 {
color: #e0e0e0;
}
body.dark-mode .text-gray-600 {
color: #b0b0b0;
}
body.dark-mode .text-gray-500 {
color: #9ca3af;
}
body.dark-mode th.text-gray-500 {
color: #d1d5db;
}
body.dark-mode .border-gray-200 {
border-color: #374151;
}
body.dark-mode .border-gray-300 {
border-color: #4b5563;
}
body.dark-mode input,
body.dark-mode select,
body.dark-mode textarea {
background-color: #1f2937;
border-color: #374151;
color: #e0e0e0;
}
body.dark-mode input:focus,
body.dark-mode select:focus,
body.dark-mode textarea:focus {
border-color: #4d7aff;
ring-color: #4d7aff;
}
body.dark-mode .bg-green-100 {
background-color: #064e3b;
color: #a7f3d0;
}
body.dark-mode .bg-red-100 {
background-color: #7f1d1d;
color: #fecaca;
}
body.dark-mode .bg-yellow-100 {
background-color: #78350f;
color: #fde68a;
}
body.dark-mode .bg-blue-100 {
background-color: #1e3a8a;
color: #bfdbfe;
}
body.dark-mode .bg-cobalt-600 {
background-color: #0b1f72;
}
body.dark-mode .bg-cobalt-700 {
background-color: #081552;
}
body.dark-mode table {
background-color: #16213e;
}
body.dark-mode th {
background-color: #1f2937;
color: #e0e0e0;
}
body.dark-mode td {
border-color: #374151;
}
body.dark-mode tr:hover {
background-color: #1f2937;
}
body.dark-mode .shadow-md,
body.dark-mode .shadow-lg {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
}
body.dark-mode code,
body.dark-mode .bg-gray-100 {
background-color: #374151;
color: #e0e0e0;
}
/* Dark mode for responsive tables */
body.dark-mode .table-responsive {
background-color: #16213e;
}
body.dark-mode .table-responsive thead {
background-color: #1f2937;
}
body.dark-mode .table-responsive th {
background-color: #1f2937;
color: #d1d5db;
border-bottom-color: #374151;
}
body.dark-mode .table-responsive td {
color: #e0e0e0;
border-bottom-color: #374151;
}
body.dark-mode .table-responsive tbody tr:hover {
background-color: #1f2937;
}
/* Dark mode for mobile cards */
body.dark-mode .mobile-card {
background-color: #16213e;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px -1px rgba(0, 0, 0, 0.3);
}
body.dark-mode .mobile-card .card-label {
color: #9ca3af;
}
body.dark-mode .mobile-card .card-value {
color: #e0e0e0;
}
body.dark-mode .mobile-card .card-row {
border-bottom-color: #374151;
}
/* Dark mode for form elements */
body.dark-mode input[type="text"],
body.dark-mode input[type="email"],
body.dark-mode input[type="number"],
body.dark-mode input[type="date"],
body.dark-mode input[type="file"],
body.dark-mode select,
body.dark-mode textarea {
background-color: #1f2937;
border-color: #374151;
color: #e0e0e0;
}
body.dark-mode input::placeholder,
body.dark-mode textarea::placeholder {
color: #6b7280;
}
/* Dark mode for buttons */
body.dark-mode .bg-gray-200 {
background-color: #374151;
color: #e0e0e0;
}
body.dark-mode .bg-gray-200:hover {
background-color: #4b5563;
}
body.dark-mode .hover\:bg-gray-300:hover {
background-color: #4b5563;
}
/* Dark mode for footer */
body.dark-mode footer {
border-color: #374151;
}
body.dark-mode footer p {
color: #9ca3af;
}
/* Dark mode for breadcrumb */
body.dark-mode .text-cobalt-600 {
color: #80a0ff;
}
body.dark-mode .hover\:text-cobalt-800:hover {
color: #b3c6ff;
}
/* Dark mode for prose (markdown preview) */
body.dark-mode .prose {
color: #e0e0e0;
}
body.dark-mode .prose h1,
body.dark-mode .prose h2,
body.dark-mode .prose h3 {
color: #ffffff;
}
body.dark-mode .prose a {
color: #80a0ff;
}
body.dark-mode .prose code {
background-color: #374151;
color: #e0e0e0;
}
body.dark-mode .prose pre {
background-color: #1f2937;
border-color: #374151;
}
body.dark-mode .prose blockquote {
border-color: #374151;
color: #b0b0b0;
}
/* Responsive Table Styles */
.table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
.table-responsive {
width: 100%;
border-collapse: collapse;
background-color: white;
}
.table-responsive thead {
background-color: #f9fafb;
}
.table-responsive th {
padding: 0.75rem 1rem;
text-align: left;
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
border-bottom: 2px solid #e5e7eb;
}
.table-responsive td {
padding: 0.75rem 1rem;
font-size: 0.875rem;
color: #374151;
border-bottom: 1px solid #e5e7eb;
white-space: nowrap;
}
.table-responsive tbody tr:hover {
background-color: #f9fafb;
}
/* Mobile Card Layout */
@media (max-width: 640px) {
.table-card-mobile {
display: none;
}
.mobile-cards {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.mobile-card {
background-color: white;
border-radius: 0.5rem;
padding: 1rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
.mobile-card .card-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.25rem 0;
border-bottom: 1px solid #f3f4f6;
}
.mobile-card .card-row:last-child {
border-bottom: none;
}
.mobile-card .card-label {
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.mobile-card .card-value {
font-size: 0.875rem;
color: #374151;
text-align: right;
}
}
</style>
<!-- Tailwind config for custom corporate colors -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#ecfdf5',
100: '#d1fae5',
200: '#a7f3d0',
300: '#6ee7b7',
400: '#34d399',
500: '#10b981',
600: '#059669',
700: '#047857',
800: '#065f46',
900: '#064e3b',
},
cobalt: {
50: '#f0f5ff',
100: '#e0eaff',
200: '#b3c6ff',
300: '#80a0ff',
400: '#4d7aff',
500: '#1338BE',
600: '#0f2a9c',
700: '#0b1f72',
800: '#081552',
900: '#060f38',
},
tiger: {
50: '#fff8f0',
100: '#ffe8d6',
200: '#ffccab',
300: '#ffae80',
400: '#ff8c55',
500: '#FC6A03',
600: '#e05a00',
700: '#b84500',
800: '#8c3300',
900: '#662500',
},
emerald: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#028A0F',
600: '#006b0c',
700: '#005209',
800: '#003d07',
900: '#002904',
}
}
}
}
}
</script>
</head>
<body class="bg-gray-50 text-gray-900">
<!-- Skip to main content -->
<a href="#main-content" class="sr-only focus:not-sr-only">Skip to main content</a>
<!-- Container -->
<div class="min-h-screen">
<!-- Header -->
<header class="bg-cobalt-600 text-white shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<!-- Logo / Title -->
<div class="flex items-center gap-3">
<img src="/static/Schamann.png" alt="Corporate Logo" class="h-10 w-auto object-contain" style="max-height: 40px; height: 40px !important;">
<h1 class="text-xl font-bold">{{ _('Tablet Management System') }}</h1>
</div>
<!-- Desktop Controls: Language + Theme Toggle -->
<div class="hidden sm:flex items-center gap-2">
<span class="text-sm opacity-80">{{ _('Language') }}:</span>
{% for lang_code, lang_name in config['LANGUAGES'].items() %}
<a
href="{{ url_for('set_language', lang=lang_code) }}"
class="px-2 py-1 text-sm rounded hover:bg-white/20 transition-colors
{% if language == lang_code %}bg-white/30 font-medium{% endif %}"
>
{{ lang_name }}
</a>
{% endfor %}
<!-- Theme Toggle -->
<a href="#" id="theme-toggle" class="text-white hover:text-cobalt-200 ml-2" title="{{ _('Toggle dark mode') }}">
<span class="icon-moon">🌙</span>
<span class="icon-sun" style="display:none;">☀️</span>
</a>
</div>
<!-- Mobile Menu Button -->
<button id="mobile-menu-btn" class="sm:hidden text-white hover:text-cobalt-200 focus:outline-none"
aria-label="Toggle navigation menu" aria-expanded="false">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path id="menu-icon-hamburger" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
<path id="menu-icon-close" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
<!-- Mobile Navigation Menu (hidden by default) -->
<div id="mobile-menu" class="sm:hidden hidden">
<div class="px-4 py-3 space-y-2 bg-cobalt-700">
<!-- Mobile Language Switcher -->
<div class="flex items-center gap-2 pb-2 border-b border-cobalt-600 mb-2">
<span class="text-sm opacity-80">{{ _('Language') }}:</span>
{% for lang_code, lang_name in config['LANGUAGES'].items() %}
<a
href="{{ url_for('set_language', lang=lang_code) }}"
class="px-2 py-1 text-sm rounded hover:bg-white/20 transition-colors
{% if language == lang_code %}bg-white/30 font-medium{% endif %}"
>
{{ lang_name }}
</a>
{% endfor %}
<!-- Mobile Theme Toggle -->
<a href="#" id="theme-toggle-mobile" class="text-white hover:text-cobalt-200 ml-2" title="{{ _('Toggle dark mode') }}">
<span class="icon-moon">🌙</span>
<span class="icon-sun" style="display:none;">☀️</span>
</a>
</div>
<!-- Mobile Navigation Links -->
<a href="/" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Home') }}
</a>
<a href="/add_tablet" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Add Tablet') }}
</a>
<a href="/add_user" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Add User') }}
</a>
<a href="/loan_tablet" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Loan Tablet') }}
</a>
<a href="/history" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('History') }}
</a>
<a href="/user_loans" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('User Loans') }}
</a>
<a href="/students" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Students') }}
</a>
<a href="/non_loanable_devices" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Non-Loanable Devices') }}
</a>
<a href="/project_management" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Project Management') }}
</a>
</div>
</div>
</header>
<!-- Main Content -->
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-6 space-y-3">
{% for category, message in messages %}
<div class="p-4 rounded-lg
{% if category == 'success' %}bg-green-100 text-green-800
{% elif category == 'error' %}bg-red-100 text-red-800
{% elif category == 'warning' %}bg-yellow-100 text-yellow-800
{% else %}bg-blue-100 text-blue-800{% endif %}">
<div class="flex items-center gap-2">
{% if category == 'success' %}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M5 13l4 4L19 7"></path>
</svg>
{% elif category == 'error' %}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
{% endif %}
<span>{{ _(message) }}</span>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- Navigation (Desktop only) -->
<nav class="hidden sm:block bg-cobalt-600 rounded-lg shadow-md mb-6" aria-label="Main navigation">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-wrap justify-center sm:justify-start gap-1 sm:gap-2 py-3">
<a href="/"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('Home') }}
</a>
<a href="/add_tablet"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('Add Tablet') }}
</a>
<a href="/add_user"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('Add User') }}
</a>
<a href="/loan_tablet"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('Loan Tablet') }}
</a>
<a href="/history"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('History') }}
</a>
<a href="/user_loans"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('User Loans') }}
</a>
<a href="/students"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('Students') }}
</a>
<a href="/non_loanable_devices"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('Non-Loanable Devices') }}
</a>
<a href="/project_management"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-cobalt-700 transition-colors whitespace-nowrap">
{{ _('Project Management') }}
</a>
</div>
</div>
</nav>
<!-- Page Content -->
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="mt-12 py-6 border-t border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-sm text-gray-500">
<p>{{ _('Tablet Management System - Internal Tool') }}</p>
</div>
</footer>
</div>
<!-- Theme Toggle Script -->
<script>
// Mobile menu toggle
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const menuIconHamburger = document.getElementById('menu-icon-hamburger');
const menuIconClose = document.getElementById('menu-icon-close');
if (mobileMenuBtn && mobileMenu) {
mobileMenuBtn.addEventListener('click', function() {
const isOpen = mobileMenu.classList.toggle('hidden');
mobileMenuBtn.setAttribute('aria-expanded', !isOpen);
// Toggle icons
menuIconHamburger.classList.toggle('hidden');
menuIconClose.classList.toggle('hidden');
});
// Close menu when clicking a link
mobileMenu.querySelectorAll('a').forEach(link => {
link.addEventListener('click', function() {
mobileMenu.classList.add('hidden');
mobileMenuBtn.setAttribute('aria-expanded', false);
menuIconHamburger.classList.remove('hidden');
menuIconClose.classList.add('hidden');
});
});
}
// Check for saved theme preference or default to light mode
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.body.classList.add('dark-mode');
updateToggleIcon(true);
} else if (savedTheme !== 'light') {
// Invalid value - clear and default to light
localStorage.removeItem('theme');
}
// Theme toggle for desktop
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', function(e) {
e.preventDefault();
const isDark = document.body.classList.toggle('dark-mode');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
updateToggleIcon(isDark);
});
}
// Theme toggle for mobile
const themeToggleMobile = document.getElementById('theme-toggle-mobile');
if (themeToggleMobile) {
themeToggleMobile.addEventListener('click', function(e) {
e.preventDefault();
const isDark = document.body.classList.toggle('dark-mode');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
updateToggleIcon(isDark);
});
}
function updateToggleIcon(isDark) {
// Update all theme toggle icons (desktop and mobile)
document.querySelectorAll('.icon-moon').forEach(icon => {
icon.style.display = isDark ? 'none' : 'inline';
});
document.querySelectorAll('.icon-sun').forEach(icon => {
icon.style.display = isDark ? 'inline' : 'none';
});
}
</script>
</body>
</html>