Improve mobile UI/UX.
This commit is contained in:
@@ -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' }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
Reference in New Issue
Block a user