commit 1eeea9f83ad9230a5c1f7a75662770eaab0df837 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 21:15:41 2025 +0200 Disable resuming of old time entries. commit 3e3ec2f01cb7943622b819a19179388078ae1315 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 20:59:19 2025 +0200 Refactor db migrations. commit 15a51a569da36c6b7c9e01ab17b6fdbdee6ad994 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 19:58:04 2025 +0200 Apply new style for Time Tracking view. commit 77e5278b303e060d2b03853b06277f8aa567ae68 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 18:06:04 2025 +0200 Allow direct registrations as a Company. commit 188a8772757cbef374243d3a5f29e4440ddecabe Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 18:04:45 2025 +0200 Add email invitation feature. commit d9ebaa02aa01b518960a20dccdd5a327d82f30c6 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 17:12:32 2025 +0200 Apply common style for Company, User, Team management pages. commit 81149caf4d8fc6317e2ab1b4f022b32fc5aa6d22 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 16:44:32 2025 +0200 Move export functions to own module. commit 1a26e19338e73f8849c671471dd15cc3c1b1fe82 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 15:51:15 2025 +0200 Split up models.py. commit 61f1ccd10f721b0ff4dc1eccf30c7a1ee13f204d Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 12:05:28 2025 +0200 Move utility function into own modules. commit 84b341ed35e2c5387819a8b9f9d41eca900ae79f Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 11:44:24 2025 +0200 Refactor auth functions use. commit 923e311e3da5b26d85845c2832b73b7b17c48adb Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 11:35:52 2025 +0200 Refactor route nameing and fix bugs along the way. commit f0a5c4419c340e62a2615c60b2a9de28204d2995 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 10:34:33 2025 +0200 Fix URL endpoints in announcement template. commit b74d74542a1c8dc350749e4788a9464d067a88b5 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 09:25:53 2025 +0200 Move announcements to own module. commit 9563a28021ac46c82c04fe4649b394dbf96f92c7 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 09:16:30 2025 +0200 Combine Company view and edit templates. commit 6687c373e681d54e4deab6b2582fed5cea9aadf6 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 08:17:42 2025 +0200 Move Users, Company and System Administration to own modules. commit 8b7894a2e3eb84bb059f546648b6b9536fea724e Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 07:40:57 2025 +0200 Move Teams and Projects to own modules. commit d11bf059d99839ecf1f5d7020b8c8c8a2454c00b Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 07:09:33 2025 +0200 Move Tasks and Sprints to own modules.
287 lines
14 KiB
HTML
287 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% if g.company %} - {{ g.company.name }}{% endif %}</title>
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts.css') }}">
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||
{% if not g.user %}
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/splash.css') }}">
|
||
{% endif %}
|
||
{% if g.branding and g.branding.favicon_filename %}
|
||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='uploads/branding/' + g.branding.favicon_filename) }}">
|
||
{% endif %}
|
||
<style>
|
||
:root {
|
||
--primary-color: {{ g.branding.primary_color if g.branding else '#007bff' }};
|
||
}
|
||
.btn-primary {
|
||
background-color: var(--primary-color);
|
||
border-color: var(--primary-color);
|
||
}
|
||
.btn-primary:hover {
|
||
background-color: {{ (g.branding.primary_color if g.branding else '#007bff') + 'dd' }};
|
||
border-color: {{ (g.branding.primary_color if g.branding else '#007bff') + 'dd' }};
|
||
}
|
||
.nav-icon {
|
||
color: var(--primary-color);
|
||
}
|
||
a:hover {
|
||
color: var(--primary-color);
|
||
}
|
||
.mobile-logo {
|
||
max-height: 30px;
|
||
max-width: 150px;
|
||
object-fit: contain;
|
||
}
|
||
.sidebar-logo {
|
||
max-height: 32px;
|
||
max-width: 160px;
|
||
object-fit: contain;
|
||
}
|
||
.mobile-nav-brand a {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.sidebar-header h2 a {
|
||
display: flex;
|
||
align-items: center;
|
||
color: inherit;
|
||
text-decoration: none;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body{% if g.user %} class="has-user"{% endif %}>
|
||
<!-- Mobile header -->
|
||
{% if g.user %}
|
||
<header class="mobile-header">
|
||
<div class="mobile-nav-brand">
|
||
<a href="{{ url_for('home') }}">
|
||
{% if g.branding and g.branding.logo_filename %}
|
||
<img src="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename) }}"
|
||
alt="{{ g.branding.logo_alt_text }}"
|
||
class="mobile-logo">
|
||
{% else %}
|
||
{{ g.branding.app_name if g.branding else 'TimeTrack' }}
|
||
{% endif %}
|
||
</a>
|
||
</div>
|
||
<button class="mobile-nav-toggle" id="mobile-nav-toggle">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</button>
|
||
</header>
|
||
{% endif %}
|
||
|
||
<!-- Sidebar navigation -->
|
||
{% if g.user %}
|
||
<aside class="sidebar" id="sidebar">
|
||
<div class="sidebar-header">
|
||
<!-- <h2>
|
||
<a href="{{ url_for('home') }}">
|
||
{% if g.branding and g.branding.logo_filename %}
|
||
<img src="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename) }}"
|
||
alt="{{ g.branding.logo_alt_text }}"
|
||
class="sidebar-logo">
|
||
{% else %}
|
||
{{ g.branding.app_name if g.branding else 'TimeTrack' }}
|
||
{% endif %}
|
||
</a>
|
||
-->
|
||
</h2>
|
||
<button class="sidebar-toggle" id="sidebar-toggle">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</button>
|
||
{% if g.user %}
|
||
<!-- User Account Menu -->
|
||
<a href="#" class="user-dropdown-toggle" id="user-dropdown-toggle" data-tooltip="{{ g.user.username }}">
|
||
<img src="{{ g.user.get_avatar_url(32) }}" alt="{{ g.user.username }}" class="user-avatar">
|
||
<span class="nav-text">{{ g.user.username }}<span class="dropdown-arrow">▼</span></span>
|
||
</a>
|
||
|
||
<!-- User Dropdown Context Menu -->
|
||
<div class="user-dropdown-modal" id="user-dropdown-modal">
|
||
<div class="user-dropdown-header">
|
||
<img src="{{ g.user.get_avatar_url(64) }}" alt="{{ g.user.username }}" class="user-avatar-large">
|
||
<h3>{{ g.user.username }}</h3>
|
||
<div class="user-info">
|
||
{% if g.user.email %}
|
||
{{ g.user.email }}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="user-dropdown-menu">
|
||
<ul>
|
||
<li><a href="{{ url_for('profile') }}"><i class="nav-icon">👤</i>Profile</a></li>
|
||
<li><a href="{{ url_for('config') }}"><i class="nav-icon">⚙️</i>Settings</a></li>
|
||
<li class="user-dropdown-divider"></li>
|
||
<li><a href="{{ url_for('logout') }}"><i class="nav-icon">🚪</i>Logout</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
<nav class="sidebar-nav">
|
||
<ul>
|
||
{% if g.user %}
|
||
<li><a href="{{ url_for('home') }}" data-tooltip="Home"><i class="nav-icon">🏠</i><span class="nav-text">Home</span></a></li>
|
||
<li><a href="{{ url_for('time_tracking') }}" data-tooltip="Time Tracking"><i class="nav-icon">⏱️</i><span class="nav-text">Time Tracking</span></a></li>
|
||
<li><a href="{{ url_for('dashboard') }}" data-tooltip="Dashboard"><i class="nav-icon">📊</i><span class="nav-text">Dashboard</span></a></li>
|
||
<li><a href="{{ url_for('tasks.unified_task_management') }}" data-tooltip="Task Management"><i class="nav-icon">📋</i><span class="nav-text">Task Management</span></a></li>
|
||
<li><a href="{{ url_for('sprints.sprint_management') }}" data-tooltip="Sprint Management"><i class="nav-icon">🏃♂️</i><span class="nav-text">Sprints</span></a></li>
|
||
<li><a href="{{ url_for('analytics') }}" data-tooltip="Time Analytics"><i class="nav-icon">📊</i><span class="nav-text">Analytics</span></a></li>
|
||
|
||
<!-- Role-based menu items -->
|
||
{% if g.user.role == Role.ADMIN or g.user.role == Role.SYSTEM_ADMIN %}
|
||
<li class="nav-divider">Admin</li>
|
||
<li><a href="{{ url_for('companies.admin_company') }}" data-tooltip="Company Settings"><i class="nav-icon">🏢</i><span class="nav-text">Company Settings</span></a></li>
|
||
<li><a href="{{ url_for('users.admin_users') }}" data-tooltip="Manage Users"><i class="nav-icon">👥</i><span class="nav-text">Manage Users</span></a></li>
|
||
<li><a href="{{ url_for('invitations.list_invitations') }}" data-tooltip="Invitations"><i class="nav-icon">📨</i><span class="nav-text">Invitations</span></a></li>
|
||
<li><a href="{{ url_for('teams.admin_teams') }}" data-tooltip="Manage Teams"><i class="nav-icon">🏭</i><span class="nav-text">Manage Teams</span></a></li>
|
||
<li><a href="{{ url_for('projects.admin_projects') }}" data-tooltip="Manage Projects"><i class="nav-icon">📝</i><span class="nav-text">Manage Projects</span></a></li>
|
||
{% if g.user.role == Role.SYSTEM_ADMIN %}
|
||
<li class="nav-divider">System Admin</li>
|
||
<li><a href="{{ url_for('system_admin.system_admin_dashboard') }}" data-tooltip="System Dashboard"><i class="nav-icon">🌐</i><span class="nav-text">System Dashboard</span></a></li>
|
||
<li><a href="{{ url_for('announcements.index') }}" data-tooltip="Announcements"><i class="nav-icon">📢</i><span class="nav-text">Announcements</span></a></li>
|
||
{% endif %}
|
||
{% elif g.user.role in [Role.TEAM_LEADER, Role.SUPERVISOR] %}
|
||
<li class="nav-divider">Team</li>
|
||
<li><a href="{{ url_for('dashboard') }}" data-tooltip="Dashboard"><i class="nav-icon">📈</i><span class="nav-text">Dashboard</span></a></li>
|
||
{% if g.user.role == Role.SUPERVISOR %}
|
||
<li><a href="{{ url_for('projects.admin_projects') }}" data-tooltip="Manage Projects"><i class="nav-icon">📝</i><span class="nav-text">Manage Projects</span></a></li>
|
||
{% endif %}
|
||
{% endif %}
|
||
{% else %}
|
||
<li><a href="{{ url_for('about') }}" data-tooltip="About"><i class="nav-icon">ℹ️</i><span class="nav-text">About</span></a></li>
|
||
<li><a href="{{ url_for('login') }}" data-tooltip="Login"><i class="nav-icon">🔑</i><span class="nav-text">Login</span></a></li>
|
||
<li><a href="{{ url_for('register') }}" data-tooltip="Register"><i class="nav-icon">📝</i><span class="nav-text">Register</span></a></li>
|
||
{% endif %}
|
||
</ul>
|
||
</nav>
|
||
</aside>
|
||
{% endif %}
|
||
|
||
<!-- Mobile overlay -->
|
||
{% if g.user %}
|
||
<div class="mobile-overlay" id="mobile-overlay"></div>
|
||
{% endif %}
|
||
|
||
<main class="main-content">
|
||
<!-- System Announcements -->
|
||
{% if active_announcements %}
|
||
<div class="announcements">
|
||
{% for announcement in active_announcements %}
|
||
<div class="alert alert-{{ announcement.announcement_type }}{% if announcement.is_urgent %} alert-urgent{% endif %}">
|
||
<div class="announcement-header">
|
||
<strong>{{ announcement.title }}</strong>
|
||
{% if announcement.is_urgent %}
|
||
<span class="urgent-badge">URGENT</span>
|
||
{% endif %}
|
||
</div>
|
||
<div class="announcement-content">
|
||
{{ announcement.content|safe }}
|
||
</div>
|
||
{% if announcement.created_at %}
|
||
<small class="announcement-date">
|
||
Posted: {{ announcement.created_at.strftime('%Y-%m-%d %H:%M') }}
|
||
</small>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||
{% if messages %}
|
||
<div class="flash-messages">
|
||
{% for category, message in messages %}
|
||
<div class="alert alert-{{ category }}">{{ message }}</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
{% endwith %}
|
||
|
||
<!-- Email Nag Screens -->
|
||
{% if g.show_email_nag %}
|
||
<div class="email-nag-banner">
|
||
<div class="email-nag-content">
|
||
<span class="email-nag-icon">📧</span>
|
||
<span class="email-nag-text">
|
||
<strong>Add your email address</strong> to enable account recovery and receive important notifications.
|
||
</span>
|
||
<a href="{{ url_for('profile') }}" class="btn btn-sm btn-primary">Add Email</a>
|
||
<button class="email-nag-dismiss" onclick="dismissEmailNag()" title="Dismiss for this session">×</button>
|
||
</div>
|
||
</div>
|
||
{% elif g.show_email_verification_nag %}
|
||
<div class="email-nag-banner email-verify">
|
||
<div class="email-nag-content">
|
||
<span class="email-nag-icon">✉️</span>
|
||
<span class="email-nag-text">
|
||
<strong>Please verify your email address</strong> to ensure you can recover your account if needed.
|
||
</span>
|
||
<a href="{{ url_for('profile') }}" class="btn btn-sm btn-warning">Verify Email</a>
|
||
<button class="email-nag-dismiss" onclick="dismissEmailNag()" title="Dismiss for this session">×</button>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% block content %}{% endblock %}
|
||
</main>
|
||
|
||
<footer class="site-footer">
|
||
<div class="footer-content">
|
||
<div class="footer-info">
|
||
<p>© {{ current_year }} {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% if g.company %} - {{ g.company.name }}{% endif %}. All rights reserved.</p>
|
||
</div>
|
||
<div class="footer-links">
|
||
<a href="{{ url_for('about') }}">About</a>
|
||
{% if g.branding and g.branding.imprint_enabled %}
|
||
<span class="footer-separator">•</span>
|
||
<a href="{{ url_for('imprint') }}">{{ g.branding.imprint_title or 'Imprint' }}</a>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/sidebar.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/password-strength.js') }}"></script>
|
||
{% if g.user %}
|
||
<script src="{{ url_for('static', filename='js/user-dropdown.js') }}"></script>
|
||
<script>
|
||
function dismissEmailNag() {
|
||
// Hide the banner with animation
|
||
const banner = document.querySelector('.email-nag-banner');
|
||
if (banner) {
|
||
banner.style.animation = 'slideUp 0.3s ease-out';
|
||
setTimeout(() => {
|
||
banner.style.display = 'none';
|
||
}, 300);
|
||
}
|
||
// Store in session storage to not show again this session
|
||
sessionStorage.setItem('emailNagDismissed', 'true');
|
||
}
|
||
|
||
// Check if already dismissed this session
|
||
if (sessionStorage.getItem('emailNagDismissed') === 'true') {
|
||
const banner = document.querySelector('.email-nag-banner');
|
||
if (banner) {
|
||
banner.style.display = 'none';
|
||
}
|
||
}
|
||
</script>
|
||
{% else %}
|
||
<script src="{{ url_for('static', filename='js/splash.js') }}"></script>
|
||
{% endif %}
|
||
|
||
<!-- Custom Tracking Script -->
|
||
{% if tracking_script_enabled and tracking_script_code %}
|
||
{{ tracking_script_code|safe }}
|
||
{% endif %}
|
||
</body>
|
||
</html> |