mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-07 21:10:46 -05:00
feat: Add Quick Wins feature set - activity tracking, templates, and user preferences
This commit introduces several high-impact features to improve user experience and productivity: New Features: - Activity Logging: Comprehensive audit trail tracking user actions across the system with Activity model, including IP address and user agent tracking - Time Entry Templates: Reusable templates for frequently logged activities with usage tracking and quick-start functionality - Saved Filters: Save and reuse common search/filter combinations across different views (projects, tasks, reports) - User Preferences: Enhanced user settings including email notifications, timezone, date/time formats, week start day, and theme preferences - Excel Export: Generate formatted Excel exports for time entries and reports with styling and proper formatting - Email Notifications: Complete email system for task assignments, overdue invoices, comments, and weekly summaries with HTML templates - Scheduled Tasks: Background task scheduler for periodic operations Models Added: - Activity: Tracks all user actions with detailed context and metadata - TimeEntryTemplate: Stores reusable time entry configurations - SavedFilter: Manages user-saved filter configurations Routes Added: - user.py: User profile and settings management - saved_filters.py: CRUD operations for saved filters - time_entry_templates.py: Template management endpoints UI Enhancements: - Bulk actions widget component - Keyboard shortcuts help modal with advanced shortcuts - Save filter widget component - Email notification templates - User profile and settings pages - Saved filters management interface - Time entry templates interface Database Changes: - Migration 022: Creates activities and time_entry_templates tables - Adds user preference columns (notifications, timezone, date/time formats) - Proper indexes for query optimization Backend Updates: - Enhanced keyboard shortcuts system (commands.js, keyboard-shortcuts-advanced.js) - Updated projects, reports, and tasks routes with activity logging - Safe database commit utilities integration - Event tracking for analytics Dependencies: - Added openpyxl for Excel generation - Added Flask-Mail dependencies - Updated requirements.txt All new features include proper error handling, activity logging integration, and maintain existing functionality while adding new capabilities.
This commit is contained in:
+47
-5
@@ -42,11 +42,31 @@
|
||||
</style>
|
||||
<script>
|
||||
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||
// Priority: User preference from server > localStorage > system preference
|
||||
{% if current_user.is_authenticated and current_user.theme %}
|
||||
var userTheme = '{{ current_user.theme }}';
|
||||
if (userTheme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
} else if (userTheme === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
} else if (userTheme === 'system') {
|
||||
// Follow system preference
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
// Fall back to localStorage or system preference
|
||||
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
{% endif %}
|
||||
</script>
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
@@ -88,10 +108,11 @@
|
||||
<span class="ml-3 sidebar-label">{{ _('Work') }}</span>
|
||||
<i class="fas fa-chevron-down ml-auto sidebar-label"></i>
|
||||
</button>
|
||||
<ul id="workDropdown" class="{% if not work_open %}hidden {% endif %}mt-2 space-y-2 ml-6">
|
||||
<ul id="workDropdown" class="{% if not work_open %}hidden {% endif %}mt-2 space-y-2 ml-6">
|
||||
{% set nav_active_projects = ep.startswith('projects.') %}
|
||||
{% set nav_active_clients = ep.startswith('clients.') %}
|
||||
{% set nav_active_tasks = ep.startswith('tasks.') %}
|
||||
{% set nav_active_templates = ep.startswith('time_entry_templates.') %}
|
||||
{% set nav_active_kanban = ep.startswith('kanban.') %}
|
||||
{% set nav_active_timer = ep.startswith('timer.') %}
|
||||
<li>
|
||||
@@ -103,6 +124,9 @@
|
||||
<li>
|
||||
<a class="block px-2 py-1 rounded {% if nav_active_tasks %}text-primary font-semibold bg-background-light dark:bg-background-dark{% else %}text-text-light dark:text-text-dark hover:bg-background-light dark:hover:bg-background-dark{% endif %}" href="{{ url_for('tasks.list_tasks') }}">{{ _('Tasks') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="block px-2 py-1 rounded {% if nav_active_templates %}text-primary font-semibold bg-background-light dark:bg-background-dark{% else %}text-text-light dark:text-text-dark hover:bg-background-light dark:hover:bg-background-dark{% endif %}" href="{{ url_for('time_entry_templates.list_templates') }}">{{ _('Time Entry Templates') }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="block px-2 py-1 rounded {% if nav_active_kanban %}text-primary font-semibold bg-background-light dark:bg-background-dark{% else %}text-text-light dark:text-text-dark hover:bg-background-light dark:hover:bg-background-dark{% endif %}" href="{{ url_for('kanban.board') }}">{{ _('Kanban') }}</a>
|
||||
</li>
|
||||
@@ -472,6 +496,9 @@
|
||||
})();
|
||||
</script>
|
||||
<!-- Command Palette Modal (restored, Tailwind-styled, no Bootstrap required) -->
|
||||
<!-- Keyboard Shortcuts Help Modal -->
|
||||
{% include 'components/keyboard_shortcuts_help.html' %}
|
||||
|
||||
<div id="commandPaletteModal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="absolute inset-0 bg-black/50" onclick="document.getElementById('commandPaletteModal').classList.add('hidden')"></div>
|
||||
<div class="relative max-w-2xl mx-auto mt-24 bg-card-light dark:bg-card-dark text-text-light dark:text-text-dark rounded-lg shadow-xl">
|
||||
@@ -526,31 +553,46 @@
|
||||
var themeToggleBtn = document.getElementById('theme-toggle');
|
||||
|
||||
themeToggleBtn.addEventListener('click', function() {
|
||||
|
||||
// toggle icons inside button
|
||||
themeToggleDarkIcon.classList.toggle('hidden');
|
||||
themeToggleLightIcon.classList.toggle('hidden');
|
||||
|
||||
var newTheme;
|
||||
// if set via local storage previously
|
||||
if (localStorage.getItem('color-theme')) {
|
||||
if (localStorage.getItem('color-theme') === 'light') {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
newTheme = 'dark';
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
newTheme = 'light';
|
||||
}
|
||||
|
||||
// if NOT set via local storage previously
|
||||
} else {
|
||||
if (document.documentElement.classList.contains('dark')) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
} else {
|
||||
newTheme = 'light';
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
newTheme = 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
// Save to database if user is logged in
|
||||
{% if current_user.is_authenticated %}
|
||||
fetch('/api/preferences', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ theme: newTheme })
|
||||
}).catch(err => console.error('Failed to save theme preference:', err));
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
function toggleDropdown(id) {
|
||||
|
||||
Reference in New Issue
Block a user