373 lines
20 KiB
HTML
373 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% if title == 'Home' %}{{ g.branding.app_name if g.branding else 'TimeTrack' }} - Enterprise Time Tracking & Project Management Software{% else %}{{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% endif %}{% if g.company %} - {{ g.company.name }}{% endif %}</title>
|
|
|
|
<!-- SEO Meta Tags -->
|
|
<meta name="description" content="{% block meta_description %}{{ g.branding.app_name if g.branding else 'TimeTrack' }} is a comprehensive time tracking solution with project management, team collaboration, billing & invoicing. Free, open-source, and enterprise-ready.{% endblock %}">
|
|
<meta name="keywords" content="{% block meta_keywords %}time tracking, project management, team collaboration, billing software, invoice management, enterprise time tracker, open source time tracking{% endblock %}">
|
|
<meta name="author" content="{{ g.branding.app_name if g.branding else 'TimeTrack' }}">
|
|
<meta name="robots" content="index, follow">
|
|
<link rel="canonical" href="{{ request.url }}">
|
|
|
|
<!-- Open Graph Meta Tags -->
|
|
<meta property="og:title" content="{% block og_title %}{{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% endblock %}">
|
|
<meta property="og:description" content="{% block og_description %}Transform your productivity with intelligent time tracking, project management, and team collaboration tools. Enterprise-grade, open-source solution.{% endblock %}">
|
|
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
|
|
<meta property="og:url" content="{{ request.url }}">
|
|
<meta property="og:site_name" content="{{ g.branding.app_name if g.branding else 'TimeTrack' }}">
|
|
{% if g.branding and g.branding.logo_filename %}
|
|
<meta property="og:image" content="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename, _external=True) }}">
|
|
{% endif %}
|
|
|
|
<!-- Twitter Card Meta Tags -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="{% block twitter_title %}{{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% endblock %}">
|
|
<meta name="twitter:description" content="{% block twitter_description %}Transform your productivity with intelligent time tracking, project management, and team collaboration tools.{% endblock %}">
|
|
{% if g.branding and g.branding.logo_filename %}
|
|
<meta name="twitter:image" content="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename, _external=True) }}">
|
|
{% endif %}
|
|
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/fonts.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/hover-standards.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/mobile-optimized.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/mobile-forms.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/mobile-bottom-nav.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/tablet-optimized.css') }}">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons@latest/iconfont/tabler-icons.min.css">
|
|
|
|
<!-- PWA Support -->
|
|
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
|
<meta name="theme-color" content="#667eea">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/icon-192x192.png') }}">
|
|
{% if g.user %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/time-tracking.css') }}">
|
|
{% endif %}
|
|
{% 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 '#667eea' }};
|
|
--primary-gradient-start: {{ g.branding.primary_color if g.branding else '#667eea' }};
|
|
--primary-gradient-end: #764ba2;
|
|
}
|
|
.nav-icon {
|
|
color: var(--primary-gradient-end);
|
|
}
|
|
.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;
|
|
}
|
|
/* Fix mobile and tablet hamburger menu visibility */
|
|
@media (max-width: 1024px) {
|
|
#mobile-nav-toggle span {
|
|
background-color: var(--primary-color, #667eea) !important;
|
|
display: block !important;
|
|
opacity: 1 !important;
|
|
visibility: visible !important;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body{% if g.user %} class="has-user has-bottom-nav"{% endif %}>
|
|
{% if g.user and g.user.preferences %}
|
|
<!-- User preferences for JavaScript -->
|
|
<div id="user-preferences" style="display: none;"
|
|
data-date-format="{{ g.user.preferences.date_format }}"
|
|
data-time-format-24h="{{ g.user.preferences.time_format_24h|lower }}">
|
|
</div>
|
|
{% 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>
|
|
<div class="mobile-nav-overlay" id="mobile-nav-overlay"></div>
|
|
{% 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 ti ti-user"></i>Profile</a></li>
|
|
<li><a href="{{ url_for('config') }}"><i class="nav-icon ti ti-settings"></i>Settings</a></li>
|
|
<li class="user-dropdown-divider"></li>
|
|
<li><a href="{{ url_for('logout') }}"><i class="nav-icon ti ti-logout"></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 ti ti-home"></i><span class="nav-text">Home</span></a></li>
|
|
<li><a href="{{ url_for('time_tracking') }}" data-tooltip="Time Tracking"><i class="nav-icon ti ti-clock"></i><span class="nav-text">Time Tracking</span></a></li>
|
|
<!-- Dashboard disabled due to widget issues -->
|
|
<!-- <li><a href="{{ url_for('dashboard') }}" data-tooltip="Dashboard"><i class="nav-icon ti ti-dashboard"></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 ti ti-clipboard-list"></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 ti ti-run"></i><span class="nav-text">Sprints</span></a></li>
|
|
<li><a href="{{ url_for('notes.notes_list') }}" data-tooltip="Notes"><i class="nav-icon ti ti-notes"></i><span class="nav-text">Notes</span></a></li>
|
|
<li><a href="{{ url_for('analytics') }}" data-tooltip="Time Analytics"><i class="nav-icon ti ti-chart-bar"></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('organization.admin_organization') }}" data-tooltip="Organization"><i class="nav-icon ti ti-sitemap"></i><span class="nav-text">Organization</span></a></li>
|
|
<li><a href="{{ url_for('companies.admin_company') }}" data-tooltip="Company Settings"><i class="nav-icon ti ti-building"></i><span class="nav-text">Company Settings</span></a></li>
|
|
<li><a href="{{ url_for('invitations.list_invitations') }}" data-tooltip="Invitations"><i class="nav-icon ti ti-mail"></i><span class="nav-text">Invitations</span></a></li>
|
|
<li><a href="{{ url_for('projects.admin_projects') }}" data-tooltip="Manage Projects"><i class="nav-icon ti ti-folder"></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 ti ti-world"></i><span class="nav-text">System Dashboard</span></a></li>
|
|
<li><a href="{{ url_for('announcements.index') }}" data-tooltip="Announcements"><i class="nav-icon ti ti-speakerphone"></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>
|
|
<!-- Dashboard disabled due to widget issues -->
|
|
<!-- <li><a href="{{ url_for('dashboard') }}" data-tooltip="Dashboard"><i class="nav-icon ti ti-chart-line"></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 ti ti-folder"></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 ti ti-info-circle"></i><span class="nav-text">About</span></a></li>
|
|
<li><a href="{{ url_for('login') }}" data-tooltip="Login"><i class="nav-icon ti ti-key"></i><span class="nav-text">Login</span></a></li>
|
|
<li><a href="{{ url_for('register') }}" data-tooltip="Register"><i class="nav-icon ti ti-user-plus"></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"><i class="ti ti-mail"></i></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"><i class="ti ti-x"></i></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"><i class="ti ti-mail-opened"></i></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"><i class="ti ti-x"></i></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>
|
|
|
|
<!-- Mobile Bottom Navigation -->
|
|
{% if g.user %}
|
|
<nav class="mobile-bottom-nav">
|
|
<a href="{{ url_for('home') }}" class="bottom-nav-item {% if request.endpoint == 'home' %}active{% endif %}">
|
|
<i class="ti ti-home"></i>
|
|
<span>Home</span>
|
|
</a>
|
|
<a href="{{ url_for('projects.admin_projects') }}" class="bottom-nav-item {% if 'project' in request.endpoint %}active{% endif %}">
|
|
<i class="ti ti-clipboard-list"></i>
|
|
<span>Projects</span>
|
|
</a>
|
|
<a href="{{ url_for('time_tracking') }}" class="bottom-nav-item nav-fab {% if 'time' in request.endpoint %}active{% endif %}">
|
|
<div class="fab-button">
|
|
<i class="ti ti-clock"></i>
|
|
</div>
|
|
<span>Time</span>
|
|
</a>
|
|
<a href="{{ url_for('notes.notes_list') }}" class="bottom-nav-item {% if 'note' in request.endpoint %}active{% endif %}">
|
|
<i class="ti ti-notes"></i>
|
|
<span>Notes</span>
|
|
</a>
|
|
<a href="{{ url_for('profile') }}" class="bottom-nav-item {% if request.endpoint == 'profile' %}active{% endif %}">
|
|
<i class="ti ti-user"></i>
|
|
<span>Profile</span>
|
|
</a>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
<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>
|
|
<script src="{{ url_for('static', filename='js/mobile-tables.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/date-formatter.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/date-picker-enhancer.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/mobile-gestures.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/mobile-pull-refresh.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/mobile-performance.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> |