Improve mobile UI/UX.

This commit is contained in:
2025-07-13 10:52:20 +02:00
parent 2d18849267
commit 7140aeba41
21 changed files with 3604 additions and 47 deletions

View File

@@ -220,7 +220,7 @@
<tr data-entry-id="{{ entry.id }}" class="entry-row">
<td>
<div class="date-cell">
<span class="date-day">{{ entry.arrival_time.strftime('%d') }}</span>
<span class="date-day">{{ entry.arrival_time.day }}</span>
<span class="date-month">{{ entry.arrival_time.strftime('%b') }}</span>
</div>
</td>
@@ -304,7 +304,7 @@
<div class="entry-card" data-entry-id="{{ entry.id }}">
<div class="entry-header">
<div class="entry-date">
{{ entry.arrival_time.strftime('%d %b %Y') }}
{{ entry.arrival_time|format_date }}
</div>
<div class="entry-duration">
{{ entry.duration|format_duration if entry.duration is not none else 'Active' }}

View File

@@ -114,7 +114,7 @@
<span class="info-icon"><i class="ti ti-calendar"></i></span>
<div class="info-content">
<label class="info-label">Created</label>
<span class="info-value">{{ company.created_at.strftime('%B %d, %Y at %I:%M %p') }}</span>
<span class="info-value">{{ company.created_at|format_datetime }}</span>
</div>
</div>
</div>

View File

@@ -240,8 +240,8 @@
</td>
<td class="date-cell">
{% if project.start_date %}
{{ project.start_date.strftime('%Y-%m-%d') }}
{% if project.end_date %}<br>to {{ project.end_date.strftime('%Y-%m-%d') }}{% endif %}
{{ project.start_date|format_date }}
{% if project.end_date %}<br>to {{ project.end_date|format_date }}{% endif %}
{% else %}
-
{% endif %}

View File

@@ -12,7 +12,7 @@
<p class="section-description">
These policies are set by your administrator and apply to all employees.
{% if g.user.role == Role.ADMIN or g.user.role == Role.SYSTEM_ADMIN %}
<a href="{{ url_for('admin_work_policies') }}">Click here to modify these settings</a>.
<a href="{{ url_for('companies.admin_company') }}">Click here to modify these settings</a>.
{% endif %}
</p>
@@ -25,20 +25,12 @@
</div>
<div class="policy-item">
<strong>Break Policy:</strong>
{% if company_config.mandatory_break_minutes > 0 %}
{% if company_config.require_breaks %}
{{ company_config.break_duration_minutes }} minutes after {{ company_config.break_after_hours }} hours
{% else %}
No mandatory breaks
{% endif %}
</div>
<div class="policy-item">
<strong>Additional Break:</strong>
{% if company_config.additional_break_minutes > 0 %}
{{ company_config.additional_break_minutes }} minutes after {{ company_config.additional_break_threshold_hours }} hours
{% else %}
No additional breaks
{% endif %}
</div>
</div>
</div>
{% endif %}

View File

@@ -1,5 +1,64 @@
{% extends "layout.html" %}
{% block meta_description %}{{ g.branding.app_name if g.branding else 'TimeTrack' }} - Free enterprise time tracking software with project management, team collaboration, billing & invoicing. Track time, manage projects, generate reports. Open-source & self-hosted.{% endblock %}
{% block meta_keywords %}time tracking software, project time tracker, team time management, billing and invoicing, enterprise time tracking, open source time tracker, self-hosted time tracking, project management software, team productivity tools, work hours tracker{% endblock %}
{% block og_title %}{{ g.branding.app_name if g.branding else 'TimeTrack' }} - Enterprise Time Tracking & Project Management{% endblock %}
{% block og_description %}Transform your team's productivity with intelligent time tracking, real-time analytics, project management, and automated billing. Free, open-source, and enterprise-ready.{% endblock %}
{% block structured_data %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "{{ g.branding.app_name if g.branding else 'TimeTrack' }}",
"url": "{{ request.host_url }}",
"description": "Free enterprise time tracking software with project management, team collaboration, billing & invoicing.",
"potentialAction": {
"@type": "SearchAction",
"@context": "https://schema.org",
"target": "{{ request.host_url }}search?q={search_term_string}",
"query-input": "required name=search_term_string"
}
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is TimeTrack?",
"acceptedAnswer": {
"@type": "Answer",
"text": "TimeTrack is a comprehensive, open-source time tracking and project management software that helps teams track work hours, manage projects, collaborate, and handle billing/invoicing - all in one platform."
}
},
{
"@type": "Question",
"name": "Is TimeTrack really free?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes! TimeTrack is 100% free and open-source. You can use our hosted version at no cost or self-host it on your own servers. There are no hidden fees, no user limits, and all features are included."
}
},
{
"@type": "Question",
"name": "What features does TimeTrack include?",
"acceptedAnswer": {
"@type": "Answer",
"text": "TimeTrack includes one-click time tracking, project management, sprint planning, team collaboration, billing & invoicing, custom reporting, multi-company support, two-factor authentication, and GDPR compliance features."
}
}
]
}
</script>
{% endblock %}
{% block content %}
{% if not g.user %}

View File

@@ -3,11 +3,47 @@
<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>
<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="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 %}
@@ -46,9 +82,25 @@
color: inherit;
text-decoration: none;
}
/* Fix mobile hamburger menu visibility */
@media (max-width: 768px) {
#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"{% endif %}>
<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">
@@ -69,6 +121,7 @@
<span></span>
</button>
</header>
<div class="mobile-nav-overlay" id="mobile-nav-overlay"></div>
{% endif %}
<!-- Sidebar navigation -->
@@ -245,10 +298,44 @@
</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>

View File

@@ -35,6 +35,15 @@
Updated {{ note.updated_at|format_date }}
</span>
{% endif %}
{% if note.folder %}
<span class="meta-divider"></span>
<span class="folder">
<span class="icon"><i class="ti ti-folder"></i></span>
<a href="{{ url_for('notes.notes_list', folder=note.folder) }}" class="folder-link">
{{ note.folder }}
</a>
</span>
{% endif %}
</div>
</div>
<div class="header-actions">
@@ -89,23 +98,9 @@
</div>
<!-- Note Metadata Card -->
{% if note.project or note.task or note.tags or note.folder %}
{% if note.project or note.task or note.tags %}
<div class="metadata-card">
<div class="metadata-grid">
{% if note.folder %}
<div class="metadata-item">
<span class="metadata-label">
<span class="icon"><i class="ti ti-folder"></i></span>
Folder
</span>
<span class="metadata-value">
<a href="{{ url_for('notes.notes_list', folder=note.folder) }}" class="metadata-link">
{{ note.folder }}
</a>
</span>
</div>
{% endif %}
{% if note.project %}
<div class="metadata-item">
<span class="metadata-label">
@@ -401,6 +396,18 @@
color: rgba(255, 255, 255, 0.5);
}
.folder-link {
color: white;
text-decoration: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
transition: border-color 0.2s ease;
}
.folder-link:hover {
color: white;
border-bottom-color: rgba(255, 255, 255, 0.8);
}
.pin-badge {
display: inline-flex;
align-items: center;

View File

@@ -158,7 +158,7 @@
</div>
<div class="info-item">
<span class="info-label">Member Since</span>
<span class="info-value">{{ user.created_at.strftime('%B %d, %Y') }}</span>
<span class="info-value">{{ user.created_at|format_date }}</span>
</div>
<div class="info-item">
<span class="info-label">Account Type</span>

View File

@@ -8,7 +8,7 @@
{% if active_entry %}
<div class="active-timer">
<h2>Currently Working</h2>
<p>Started at: {{ active_entry.arrival_time.strftime('%Y-%m-%d %H:%M:%S') }}</p>
<p>Started at: {{ active_entry.arrival_time|format_datetime }}</p>
<div id="timer" data-start="{{ active_entry.arrival_time.timestamp() }}">00:00:00</div>
<button id="leave-btn" class="leave-btn" data-id="{{ active_entry.id }}">Leave</button>
</div>
@@ -35,11 +35,11 @@
</thead>
<tbody id="time-history-body">
{% for entry in history %}
<tr data-entry-id="{{ entry.id }}">
<td>{{ entry.arrival_time.strftime('%Y-%m-%d') }}</td>
<td>{{ entry.arrival_time.strftime('%H:%M:%S') }}</td>
<td>{{ entry.departure_time.strftime('%H:%M:%S') if entry.departure_time else 'Active' }}</td>
<td>{{ (entry.duration // 3600)|string + 'h ' + ((entry.duration % 3600) // 60)|string + 'm' if entry.duration else 'N/A' }}</td>
<tr data-entry-id="{{ entry.id }}" data-iso-date="{{ entry.arrival_time.strftime('%Y-%m-%d') }}">
<td>{{ entry.arrival_time|format_date }}</td>
<td>{{ entry.arrival_time|format_time }}</td>
<td>{{ entry.departure_time|format_time if entry.departure_time else 'Active' }}</td>
<td>{{ entry.duration|format_duration if entry.duration else 'N/A' }}</td>
<td>
<button class="edit-entry-btn" data-id="{{ entry.id }}">Edit</button>
<button class="delete-entry-btn" data-id="{{ entry.id }}">Delete</button>
@@ -107,17 +107,18 @@
const arrivalTimeStr = cells[1].textContent;
const departureTimeStr = cells[2].textContent !== 'Active' ? cells[2].textContent : '';
// Parse date and time
const arrivalDate = new Date(`${dateStr}T${arrivalTimeStr}`);
// For edit form, we need ISO format (YYYY-MM-DD)
// Parse the displayed date back to ISO format
const entryRow = document.querySelector(`tr[data-entry-id="${entryId}"]`);
const isoDate = entryRow.dataset.isoDate || dateStr;
// Set values in the form
document.getElementById('edit-entry-id').value = entryId;
document.getElementById('edit-arrival-date').value = dateStr;
document.getElementById('edit-arrival-date').value = isoDate;
document.getElementById('edit-arrival-time').value = arrivalTimeStr;
if (departureTimeStr) {
const departureDate = new Date(`${dateStr}T${departureTimeStr}`);
document.getElementById('edit-departure-date').value = dateStr;
document.getElementById('edit-departure-date').value = isoDate;
document.getElementById('edit-departure-time').value = departureTimeStr;
} else {
document.getElementById('edit-departure-date').value = '';