feat(ui): unify page headers across app; add Buy Me a Coffee link

- Create and use shared page_header macro for consistent info headers
- Apply new header styling to:
  - Dashboard, Projects, Clients, Invoices (removed filter dropdown), Reports, Timer (manual)
  - System Info, Admin Users (list + form)
  - Profile, Edit Profile
  - Tasks (list, my tasks, overdue)
  - Analytics (desktop + mobile)
  - About, Help
- Add shared header/badge utilities in base.css for consistent spacing and badges
- Align summary cards and table/button styles with admin dashboard
- Footer: add “Buy me a coffee” link (https://buymeacoffee.com/DryTrix)
This commit is contained in:
Dries Peeters
2025-09-04 09:52:09 +02:00
parent 1d29393008
commit 3376874ba0
21 changed files with 508 additions and 487 deletions

View File

@@ -1031,3 +1031,20 @@ h6 { font-size: 1rem; }
.summary-value { font-size: 18px; }
}
/* Shared page header utilities to align with admin dashboard */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.5rem;
}
.page-header .h3 { margin: 0; }
/* Consistent badge sizing used next to page titles */
.badge.fs-6 {
line-height: 1;
padding: 0.5rem 0.75rem;
border-radius: 9999px;
}

View File

@@ -0,0 +1,35 @@
{% macro page_header(icon_class, title_text, subtitle_text=None, actions_html=None) %}
<div class="card stats-card hover-lift mb-4">
<div class="card-body d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center">
<div class="mb-3 mb-md-0">
<h1 class="h3 mb-1">{% if icon_class %}<i class="{{ icon_class }} me-2"></i>{% endif %}{{ title_text }}</h1>
{% if subtitle_text %}
<p class="mb-0">{{ subtitle_text }}</p>
{% endif %}
</div>
<div class="d-flex flex-wrap gap-2">
{{ actions_html|safe if actions_html }}
</div>
</div>
</div>
{% endmacro %}
{% macro summary_card(icon_class, icon_color, label, value) %}
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-{{ icon_color }} bg-opacity-10 text-{{ icon_color }}">
<i class="{{ icon_class }}"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">{{ label }}</div>
<div class="summary-value">{{ value }}</div>
</div>
</div>
</div>
</div>
{% endmacro %}

View File

@@ -4,24 +4,21 @@
{% block content %}
<div class="container-fluid">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
<i class="fas fa-chart-line text-primary"></i> Analytics Dashboard
</h1>
<div class="d-flex gap-2">
<select id="timeRange" class="form-select form-select-sm" style="width: auto;">
<option value="7">Last 7 days</option>
<option value="30" selected>Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="365">Last year</option>
</select>
<button id="refreshCharts" class="btn btn-outline-primary btn-sm">
<i class="fas fa-sync-alt"></i> Refresh
</button>
</div>
</div>
{% set actions %}
<select id="timeRange" class="form-select form-select-sm" style="width: auto;">
<option value="7">Last 7 days</option>
<option value="30" selected>Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="365">Last year</option>
</select>
<button id="refreshCharts" class="btn btn-outline-light btn-sm">
<i class="fas fa-sync-alt"></i> Refresh
</button>
{% endset %}
{{ page_header('fas fa-chart-line', 'Analytics Dashboard', 'Key metrics and insights', actions) }}
</div>
</div>

View File

@@ -4,23 +4,20 @@
{% block content %}
<div class="container-fluid px-3">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">
<i class="fas fa-chart-line text-primary"></i> Analytics
</h1>
<div class="d-flex gap-2">
<select id="timeRange" class="form-select form-select-sm" style="width: auto; font-size: 0.875rem;">
<option value="7">7d</option>
<option value="30" selected>30d</option>
<option value="90">90d</option>
</select>
<button id="refreshCharts" class="btn btn-outline-primary btn-sm">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
{% set actions %}
<select id="timeRange" class="form-select form-select-sm" style="width: auto; font-size: 0.875rem;">
<option value="7">7d</option>
<option value="30" selected>30d</option>
<option value="90">90d</option>
</select>
<button id="refreshCharts" class="btn btn-outline-light btn-sm">
<i class="fas fa-sync-alt"></i>
</button>
{% endset %}
{{ page_header('fas fa-chart-line', 'Analytics', 'Mobile insights overview', actions) }}
</div>
</div>

View File

@@ -3,12 +3,21 @@
{% block title %}Edit Profile - {{ app_name }}{% endblock %}
{% block content %}
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
{% set actions %}
<a href="{{ url_for('auth.profile') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-arrow-left"></i> Back to Profile
</a>
{% endset %}
{{ page_header('fas fa-user-cog', 'Edit Profile', 'Update your personal information', actions) }}
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<i class="fas fa-user-cog me-2"></i>Edit Profile
</div>
<div class="card-header"><i class="fas fa-id-card me-2"></i> Profile Details</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">

View File

@@ -3,15 +3,21 @@
{% block title %}Profile - {{ app_name }}{% endblock %}
{% block content %}
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
{% set actions %}
<a href="{{ url_for('auth.edit_profile') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-edit"></i> Edit Profile
</a>
{% endset %}
{{ page_header('fas fa-user-circle', 'Your Profile', 'Manage your account details', actions) }}
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-user-circle me-2"></i>Your Profile</span>
<a href="{{ url_for('auth.edit_profile') }}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit me-1"></i>Edit Profile
</a>
</div>
<div class="card-header"><i class="fas fa-id-card me-2"></i> Profile Details</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-sm-4 text-muted">Username</div>

View File

@@ -173,10 +173,15 @@
</p>
</div>
<div class="col-md-6 text-md-end">
<div class="d-flex justify-content-md-end gap-3">
<div class="d-flex justify-content-md-end gap-3 align-items-center">
<small class="text-muted">{{ app_version }}</small>
<small><a href="{{ url_for('main.about') }}" class="text-decoration-none">About</a></small>
<small><a href="{{ url_for('main.help') }}" class="text-decoration-none">Help</a></small>
<small>
<a href="https://buymeacoffee.com/DryTrix" target="_blank" rel="noopener" class="text-decoration-none">
<i class="fas fa-mug-hot me-1"></i> Buy me a coffee
</a>
</small>
</div>
</div>
</div>

View File

@@ -3,40 +3,25 @@
{% block title %}Dashboard - {{ app_name }}{% endblock %}
{% block content %}
{% from "_components.html" import page_header %}
<!-- Toast Container -->
<div id="toast-container" class="toast-container position-fixed top-0 end-0 p-3"></div>
<!-- Template meta for JS flags -->
<div id="dashboard-meta" data-has-active-timer="{{ 1 if active_timer else 0 }}" style="display:none;"></div>
<!-- Enhanced Welcome Section -->
<div class="row section-spacing">
<!-- Page Header matching admin style -->
<div class="row">
<div class="col-12">
<div class="card mobile-card hover-lift">
<div class="card-body py-4">
<div class="row align-items-center">
<div class="col-lg-8 col-md-7 mb-3 mb-md-0">
<div class="d-flex align-items-center mb-3">
{% if settings and settings.has_logo() %}
<img src="{{ settings.get_logo_url() }}" alt="Company Logo" class="me-3" width="32" height="32">
{% else %}
<img src="{{ url_for('static', filename='images/drytrix-logo.svg') }}" alt="DryTrix Logo" class="me-3" width="32" height="32">
{% endif %}
<h2 class="mb-0">Welcome back, {{ current_user.display_name }}!</h2>
</div>
<p class="text-muted mb-0 fs-5">Track your productivity and manage your time effectively with <strong>DryTrix</strong> TimeTracker</p>
</div>
<div class="col-lg-4 col-md-5 text-center text-md-end">
<div class="d-flex justify-content-center justify-content-md-end">
<div class="text-center">
<div class="h1 text-primary mb-2 fw-bold">{{ today_hours|round(1) }}</div>
<small class="text-muted fs-6">Hours Today</small>
</div>
</div>
</div>
</div>
</div>
</div>
{% set actions %}
<a href="{{ url_for('timer.manual_entry') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-plus"></i> Log Time
</a>
<a href="{{ url_for('reports.reports') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-chart-line"></i> Reports
</a>
{% endset %}
{{ page_header('fas fa-tachometer-alt', 'Dashboard', 'Welcome back, ' ~ current_user.display_name ~ ' • ' ~ (today_hours|round(1)) ~ 'h today', actions) }}
</div>
</div>

View File

@@ -4,24 +4,16 @@
{% block content %}
<div class="container mt-4">
<!-- Header Section (Invoices-style) -->
{% from "_components.html" import page_header %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<h1 class="h3 mb-0 me-3">
<i class="fas fa-tasks text-primary"></i>
Tasks
</h1>
<span class="badge bg-primary fs-6">{{ tasks|length }} total</span>
</div>
<div class="d-flex gap-2">
<a href="{{ url_for('tasks.create_task') }}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> New Task
</a>
</div>
</div>
{% set actions %}
<a href="{{ url_for('tasks.create_task') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-plus"></i> New Task
</a>
{% endset %}
{{ page_header('fas fa-tasks', 'Tasks', 'Plan and track work • ' ~ (tasks|length) ~ ' total', actions) }}
</div>
</div>
</div>

View File

@@ -4,34 +4,16 @@
{% block content %}
<div class="container mt-4">
<!-- Header Section (Invoices-style) -->
{% from "_components.html" import page_header %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<h1 class="h3 mb-0 me-3">
<i class="fas fa-user text-primary"></i>
My Tasks
</h1>
<span class="badge bg-primary fs-6">{{ tasks|length }} total</span>
</div>
<div class="d-flex gap-2">
<div class="dropdown">
<button class="btn btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-filter me-1"></i> Filter
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ url_for('tasks.list_tasks') }}">All Tasks</a></li>
<li><a class="dropdown-item" href="{{ url_for('tasks.my_tasks', task_type='assigned') }}">Assigned to Me</a></li>
<li><a class="dropdown-item" href="{{ url_for('tasks.my_tasks', task_type='created') }}">Created by Me</a></li>
</ul>
</div>
<a href="{{ url_for('tasks.create_task') }}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> New Task
</a>
</div>
</div>
{% set actions %}
<a href="{{ url_for('tasks.create_task') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-plus"></i> New Task
</a>
{% endset %}
{{ page_header('fas fa-user', 'My Tasks', 'Tasks assigned to or created by you • ' ~ (tasks|length) ~ ' total', actions) }}
</div>
</div>
</div>

View File

@@ -4,14 +4,13 @@
{% block content %}
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Overdue Tasks</h1>
<div>
<a href="{{ url_for('tasks.list_tasks') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Back to Tasks
</a>
</div>
</div>
{% from "_components.html" import page_header %}
{% set actions %}
<a href="{{ url_for('tasks.list_tasks') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-arrow-left"></i> Back to Tasks
</a>
{% endset %}
{{ page_header('fas fa-exclamation-triangle', 'Overdue Tasks', 'Requires immediate attention • ' ~ (tasks|length) ~ ' items', actions) }}
<!-- Overdue Summary -->
<div class="alert alert-warning" role="alert">

View File

@@ -3,12 +3,66 @@
{% block title %}System Info - {{ app_name }}{% endblock %}
{% block content %}
<div class="container">
{% from "_components.html" import page_header %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
{{ page_header('fas fa-info-circle', 'System Information', 'System status and metrics', None) }}
</div>
</div>
<div class="row mb-4">
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3 d-flex align-items-center">
<div class="summary-icon bg-primary bg-opacity-10 text-primary"><i class="fas fa-users"></i></div>
<div class="ms-3">
<div class="summary-label">Total Users</div>
<div class="summary-value">{{ total_users }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3 d-flex align-items-center">
<div class="summary-icon bg-success bg-opacity-10 text-success"><i class="fas fa-project-diagram"></i></div>
<div class="ms-3">
<div class="summary-label">Total Projects</div>
<div class="summary-value">{{ total_projects }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3 d-flex align-items-center">
<div class="summary-icon bg-info bg-opacity-10 text-info"><i class="fas fa-clock"></i></div>
<div class="ms-3">
<div class="summary-label">Time Entries</div>
<div class="summary-value">{{ total_entries }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3 d-flex align-items-center">
<div class="summary-icon bg-warning bg-opacity-10 text-warning"><i class="fas fa-play"></i></div>
<div class="ms-3">
<div class="summary-label">Active Timers</div>
<div class="summary-value">{{ active_timers }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<i class="fas fa-info-circle me-2"></i> System Information
<i class="fas fa-info-circle me-2"></i> System Details
</div>
<div class="card-body">
<div class="row mb-3">

View File

@@ -4,28 +4,15 @@
{% block content %}
<div class="container-fluid">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('admin.admin_dashboard') }}">Admin</a></li>
<li class="breadcrumb-item"><a href="{{ url_for('admin.list_users') }}">Users</a></li>
<li class="breadcrumb-item active">{% if user %}Edit{% else %}New{% endif %}</li>
</ol>
</nav>
<h1 class="h3 mb-0">
<i class="fas fa-user text-primary"></i>
{% if user %}Edit User{% else %}New User{% endif %}
</h1>
</div>
<div>
<a href="{{ url_for('admin.list_users') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Users
</a>
</div>
</div>
{% set actions %}
<a href="{{ url_for('admin.list_users') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-arrow-left"></i> Back to Users
</a>
{% endset %}
{{ page_header('fas fa-user', (user and 'Edit User' or 'New User'), 'Create or update user accounts', actions) }}
</div>
</div>

View File

@@ -4,30 +4,15 @@
{% block content %}
<div class="container-fluid">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-start flex-column flex-md-row">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('admin.admin_dashboard') }}">Admin</a></li>
<li class="breadcrumb-item active">Users</li>
</ol>
</nav>
<div class="d-flex align-items-center">
<h1 class="h3 mb-0 me-3">
<i class="fas fa-users text-primary"></i>
User Management
</h1>
<span class="badge bg-primary fs-6">{{ users|length }} total</span>
</div>
</div>
<div>
<a href="{{ url_for('admin.create_user') }}" class="btn btn-primary">
<i class="fas fa-user-plus"></i> New User
</a>
</div>
</div>
{% set actions %}
<a href="{{ url_for('admin.create_user') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-user-plus"></i> New User
</a>
{% endset %}
{{ page_header('fas fa-users', 'User Management', 'Manage users • ' ~ (users|length) ~ ' total', actions) }}
</div>
</div>

View File

@@ -4,24 +4,17 @@
{% block content %}
<div class="container-fluid">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<h1 class="h3 mb-0 me-3">
<i class="fas fa-building text-primary"></i>
Clients
</h1>
<span class="badge bg-primary fs-6">{{ clients|length }} total</span>
</div>
<div class="d-flex gap-2">
{% if current_user.is_admin %}
<a href="{{ url_for('clients.create_client') }}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> New Client
</a>
{% endif %}
</div>
</div>
{% set actions %}
{% if current_user.is_admin %}
<a href="{{ url_for('clients.create_client') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-plus"></i> New Client
</a>
{% endif %}
{% endset %}
{{ page_header('fas fa-building', 'Clients', 'Manage customers and contacts • ' ~ (clients|length) ~ ' total', actions) }}
</div>
</div>

View File

@@ -4,283 +4,263 @@
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<!-- Header with Enhanced Actions -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<h1 class="h3 mb-0 me-3">
<i class="fas fa-file-invoice-dollar text-primary"></i>
Invoices
</h1>
<span class="badge bg-primary fs-6">{{ summary.total_invoices }} total</span>
</div>
<div class="d-flex gap-2">
<div class="dropdown">
<button class="btn btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-filter me-1"></i> Filter
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="filterByStatus('all')">All Invoices</a></li>
<li><a class="dropdown-item" href="#" onclick="filterByStatus('draft')">Drafts</a></li>
<li><a class="dropdown-item" href="#" onclick="filterByStatus('sent')">Sent</a></li>
<li><a class="dropdown-item" href="#" onclick="filterByStatus('paid')">Paid</a></li>
<li><a class="dropdown-item" href="#" onclick="filterByStatus('overdue')">Overdue</a></li>
</ul>
</div>
<a href="{{ url_for('invoices.create_invoice') }}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i> Create Invoice
</a>
</div>
</div>
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
{% set actions %}
<a href="{{ url_for('invoices.create_invoice') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-plus"></i> Create Invoice
</a>
{% endset %}
{{ page_header('fas fa-file-invoice-dollar', 'Invoices', 'Billing overview • ' ~ summary.total_invoices ~ ' total', actions) }}
</div>
</div>
<!-- Enhanced Summary Cards -->
<div class="row mb-4">
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-primary bg-opacity-10 text-primary">
<i class="fas fa-file-invoice"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Total Invoices</div>
<div class="summary-value">{{ summary.total_invoices }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- Enhanced Summary Cards -->
<div class="row mb-4">
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-primary bg-opacity-10 text-primary">
<i class="fas fa-file-invoice"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Total Invoices</div>
<div class="summary-value">{{ summary.total_invoices }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-success bg-opacity-10 text-success">
<i class="fas fa-dollar-sign"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Total Amount</div>
<div class="summary-value">{{ "%.2f"|format(summary.total_amount) }} {{ currency }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-success bg-opacity-10 text-success">
<i class="fas fa-dollar-sign"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Total Amount</div>
<div class="summary-value">{{ "%.2f"|format(summary.total_amount) }} {{ currency }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-info bg-opacity-10 text-info">
<i class="fas fa-clock"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Outstanding</div>
<div class="summary-value">{{ "%.2f"|format(summary.outstanding_amount) }} {{ currency }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-info bg-opacity-10 text-info">
<i class="fas fa-clock"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Outstanding</div>
<div class="summary-value">{{ "%.2f"|format(summary.outstanding_amount) }} {{ currency }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-warning bg-opacity-10 text-warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Overdue</div>
<div class="summary-value">{{ "%.2f"|format(summary.overdue_amount) }} {{ currency }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6 mb-3">
<div class="card border-0 shadow-sm h-100 summary-card">
<div class="card-body p-3">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="summary-icon bg-warning bg-opacity-10 text-warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<div class="summary-label">Overdue</div>
<div class="summary-value">{{ "%.2f"|format(summary.overdue_amount) }} {{ currency }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Enhanced Invoices Table -->
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-3">
<div class="d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-list me-2"></i>All Invoices
</h6>
<div class="d-flex gap-2">
<div class="input-group input-group-sm" style="width: 250px;">
<span class="input-group-text bg-light border-end-0">
<i class="fas fa-search text-muted"></i>
</span>
<input type="text" class="form-control border-start-0" id="searchInput"
placeholder="Search invoices...">
</div>
</div>
</div>
</div>
<div class="card-body p-0">
{% if invoices %}
<div class="table-responsive">
<table class="table table-hover mb-0" id="invoicesTable">
<thead class="table-light">
<tr>
<th class="border-0">Invoice #</th>
<th class="border-0">Client</th>
<th class="border-0">Project</th>
<th class="border-0">Issue Date</th>
<th class="border-0">Due Date</th>
<th class="border-0">Amount</th>
<th class="border-0">Status</th>
<th class="border-0 text-center">Actions</th>
</tr>
</thead>
<tbody>
{% for invoice in invoices %}
<tr class="invoice-row" data-status="{{ invoice.status }}">
<td class="align-middle">
<div class="d-flex align-items-center">
<div class="invoice-number-badge me-2">
{{ invoice.invoice_number }}
</div>
</div>
</td>
<td class="align-middle">
<div class="client-info">
<div class="client-name">{{ invoice.client_name }}</div>
{% if invoice.client_email %}
<small class="text-muted">{{ invoice.client_email }}</small>
{% endif %}
</div>
</td>
<td class="align-middle">
<span class="project-badge">{{ invoice.project.name }}</span>
</td>
<td class="align-middle">
<div class="date-info">
<div class="date-value">{{ invoice.issue_date.strftime('%b %d, %Y') }}</div>
<small class="text-muted">{{ invoice.issue_date.strftime('%Y-%m-%d') }}</small>
</div>
</td>
<td class="align-middle">
{% if invoice.is_overdue %}
<div class="due-date overdue">
<div class="date-value text-danger">
{{ invoice.due_date.strftime('%b %d, %Y') }}
</div>
<small class="text-danger">
<i class="fas fa-exclamation-circle me-1"></i>
{{ invoice.days_overdue }} days overdue
</small>
</div>
{% else %}
<div class="due-date">
<div class="date-value">{{ invoice.due_date.strftime('%b %d, %Y') }}</div>
<small class="text-muted">{{ invoice.due_date.strftime('%Y-%m-%d') }}</small>
</div>
{% endif %}
</td>
<td class="align-middle">
<div class="amount-info">
<div class="amount-value">{{ "%.2f"|format(invoice.total_amount) }} {{ currency }}</div>
</div>
</td>
<td class="align-middle">
{% set status_config = {
'draft': {'color': 'secondary', 'icon': 'edit', 'bg': 'bg-secondary'},
'sent': {'color': 'info', 'icon': 'paper-plane', 'bg': 'bg-info'},
'paid': {'color': 'success', 'icon': 'check-circle', 'bg': 'bg-success'},
'overdue': {'color': 'danger', 'icon': 'exclamation-triangle', 'bg': 'bg-danger'},
'cancelled': {'color': 'dark', 'icon': 'times-circle', 'bg': 'bg-dark'}
} %}
{% set config = status_config.get(invoice.status, status_config.draft) %}
<span class="status-badge {{ config.bg }} text-white">
<i class="fas fa-{{ config.icon }} me-1"></i>
{{ invoice.status|title }}
</span>
</td>
<td class="align-middle text-center">
<div class="btn-group btn-group-sm" role="group">
<a href="{{ url_for('invoices.view_invoice', invoice_id=invoice.id) }}"
class="btn btn-sm btn-action btn-action--view"
title="View Invoice">
<i class="fas fa-eye"></i>
</a>
<a href="{{ url_for('invoices.edit_invoice', invoice_id=invoice.id) }}"
class="btn btn-sm btn-action btn-action--edit"
title="Edit Invoice">
<i class="fas fa-edit"></i>
</a>
<div class="btn-group" role="group">
<button type="button"
class="btn btn-sm btn-action btn-action--more dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false">
<span class="visually-hidden">More actions</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item"
href="{{ url_for('invoices.export_invoice_csv', invoice_id=invoice.id) }}">
<i class="fas fa-download me-2"></i> Export CSV
</a>
</li>
<li>
<a class="dropdown-item"
href="{{ url_for('invoices.duplicate_invoice', invoice_id=invoice.id) }}">
<i class="fas fa-copy me-2"></i> Duplicate
</a>
</li>
<li>
<a class="dropdown-item"
href="{{ url_for('invoices.generate_from_time', invoice_id=invoice.id) }}">
<i class="fas fa-clock me-2"></i> Generate from Time
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<form method="POST"
action="{{ url_for('invoices.delete_invoice', invoice_id=invoice.id) }}"
class="d-inline"
onsubmit="return confirm('Are you sure you want to delete this invoice? This action cannot be undone.')">
<button type="submit" class="dropdown-item text-danger">
<i class="fas fa-trash me-2"></i> Delete
</button>
</form>
</li>
</ul>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<div class="empty-state">
<i class="fas fa-file-invoice fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No invoices found</h5>
<p class="text-muted mb-4">Create your first invoice to get started with billing.</p>
<a href="{{ url_for('invoices.create_invoice') }}" class="btn btn-primary btn-lg">
<i class="fas fa-plus me-2"></i> Create Your First Invoice
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Enhanced Invoices Table -->
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-3">
<div class="d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-list me-2"></i>All Invoices
</h6>
<div class="d-flex gap-2">
<div class="input-group input-group-sm" style="width: 250px;">
<span class="input-group-text bg-light border-end-0">
<i class="fas fa-search text-muted"></i>
</span>
<input type="text" class="form-control border-start-0" id="searchInput"
placeholder="Search invoices...">
</div>
</div>
</div>
</div>
<div class="card-body p-0">
{% if invoices %}
<div class="table-responsive">
<table class="table table-hover mb-0" id="invoicesTable">
<thead class="table-light">
<tr>
<th class="border-0">Invoice #</th>
<th class="border-0">Client</th>
<th class="border-0">Project</th>
<th class="border-0">Issue Date</th>
<th class="border-0">Due Date</th>
<th class="border-0">Amount</th>
<th class="border-0">Status</th>
<th class="border-0 text-center">Actions</th>
</tr>
</thead>
<tbody>
{% for invoice in invoices %}
<tr class="invoice-row" data-status="{{ invoice.status }}">
<td class="align-middle">
<div class="d-flex align-items-center">
<div class="invoice-number-badge me-2">
{{ invoice.invoice_number }}
</div>
</div>
</td>
<td class="align-middle">
<div class="client-info">
<div class="client-name">{{ invoice.client_name }}</div>
{% if invoice.client_email %}
<small class="text-muted">{{ invoice.client_email }}</small>
{% endif %}
</div>
</td>
<td class="align-middle">
<span class="project-badge">{{ invoice.project.name }}</span>
</td>
<td class="align-middle">
<div class="date-info">
<div class="date-value">{{ invoice.issue_date.strftime('%b %d, %Y') }}</div>
<small class="text-muted">{{ invoice.issue_date.strftime('%Y-%m-%d') }}</small>
</div>
</td>
<td class="align-middle">
{% if invoice.is_overdue %}
<div class="due-date overdue">
<div class="date-value text-danger">
{{ invoice.due_date.strftime('%b %d, %Y') }}
</div>
<small class="text-danger">
<i class="fas fa-exclamation-circle me-1"></i>
{{ invoice.days_overdue }} days overdue
</small>
</div>
{% else %}
<div class="due-date">
<div class="date-value">{{ invoice.due_date.strftime('%b %d, %Y') }}</div>
<small class="text-muted">{{ invoice.due_date.strftime('%Y-%m-%d') }}</small>
</div>
{% endif %}
</td>
<td class="align-middle">
<div class="amount-info">
<div class="amount-value">{{ "%.2f"|format(invoice.total_amount) }} {{ currency }}</div>
</div>
</td>
<td class="align-middle">
{% set status_config = {
'draft': {'color': 'secondary', 'icon': 'edit', 'bg': 'bg-secondary'},
'sent': {'color': 'info', 'icon': 'paper-plane', 'bg': 'bg-info'},
'paid': {'color': 'success', 'icon': 'check-circle', 'bg': 'bg-success'},
'overdue': {'color': 'danger', 'icon': 'exclamation-triangle', 'bg': 'bg-danger'},
'cancelled': {'color': 'dark', 'icon': 'times-circle', 'bg': 'bg-dark'}
} %}
{% set config = status_config.get(invoice.status, status_config.draft) %}
<span class="status-badge {{ config.bg }} text-white">
<i class="fas fa-{{ config.icon }} me-1"></i>
{{ invoice.status|title }}
</span>
</td>
<td class="align-middle text-center">
<div class="btn-group btn-group-sm" role="group">
<a href="{{ url_for('invoices.view_invoice', invoice_id=invoice.id) }}"
class="btn btn-sm btn-action btn-action--view"
title="View Invoice">
<i class="fas fa-eye"></i>
</a>
<a href="{{ url_for('invoices.edit_invoice', invoice_id=invoice.id) }}"
class="btn btn-sm btn-action btn-action--edit"
title="Edit Invoice">
<i class="fas fa-edit"></i>
</a>
<div class="btn-group" role="group">
<button type="button"
class="btn btn-sm btn-action btn-action--more dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false">
<span class="visually-hidden">More actions</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item"
href="{{ url_for('invoices.export_invoice_csv', invoice_id=invoice.id) }}">
<i class="fas fa-download me-2"></i> Export CSV
</a>
</li>
<li>
<a class="dropdown-item"
href="{{ url_for('invoices.duplicate_invoice', invoice_id=invoice.id) }}">
<i class="fas fa-copy me-2"></i> Duplicate
</a>
</li>
<li>
<a class="dropdown-item"
href="{{ url_for('invoices.generate_from_time', invoice_id=invoice.id) }}">
<i class="fas fa-clock me-2"></i> Generate from Time
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<form method="POST"
action="{{ url_for('invoices.delete_invoice', invoice_id=invoice.id) }}"
class="d-inline"
onsubmit="return confirm('Are you sure you want to delete this invoice? This action cannot be undone.')">
<button type="submit" class="dropdown-item text-danger">
<i class="fas fa-trash me-2"></i> Delete
</button>
</form>
</li>
</ul>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<div class="empty-state">
<i class="fas fa-file-invoice fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No invoices found</h5>
<p class="text-muted mb-4">Create your first invoice to get started with billing.</p>
<a href="{{ url_for('invoices.create_invoice') }}" class="btn btn-primary btn-lg">
<i class="fas fa-plus me-2"></i> Create Your First Invoice
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<style>

View File

@@ -3,7 +3,13 @@
{% block title %}About - {{ app_name }}{% endblock %}
{% block content %}
<div class="container">
{% from "_components.html" import page_header %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
{{ page_header('fas fa-info-circle', 'About', 'Learn more about ' ~ app_name, None) }}
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="text-center mb-5">

View File

@@ -3,7 +3,13 @@
{% block title %}Help - {{ app_name }}{% endblock %}
{% block content %}
<div class="container">
{% from "_components.html" import page_header %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
{{ page_header('fas fa-question-circle', 'Help', 'Documentation and guidance', None) }}
</div>
</div>
<div class="row">
<div class="col-lg-3">
<!-- Navigation -->

View File

@@ -4,24 +4,17 @@
{% block content %}
<div class="container-fluid">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<h1 class="h3 mb-0 me-3">
<i class="fas fa-project-diagram text-primary"></i>
Projects
</h1>
<span class="badge bg-primary fs-6">{{ projects|length }} total</span>
</div>
<div class="d-flex gap-2">
{% if current_user.is_admin %}
<a href="{{ url_for('projects.create_project') }}" class="btn btn-primary touch-target">
<i class="fas fa-plus me-1"></i> New Project
</a>
{% endif %}
</div>
</div>
{% set actions %}
{% if current_user.is_admin %}
<a href="{{ url_for('projects.create_project') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-plus"></i> New Project
</a>
{% endif %}
{% endset %}
{{ page_header('fas fa-project-diagram', 'Projects', 'Manage all projects • ' ~ (projects|length) ~ ' total', actions) }}
</div>
</div>

View File

@@ -4,18 +4,15 @@
{% block content %}
<div class="container-fluid">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
<i class="fas fa-chart-line text-primary"></i> Reports
</h1>
<div>
<a href="{{ url_for('reports.export_csv') }}" class="btn btn-outline-primary">
<i class="fas fa-download"></i> Export CSV
</a>
</div>
</div>
{% set actions %}
<a href="{{ url_for('reports.export_csv') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-download"></i> Export CSV
</a>
{% endset %}
{{ page_header('fas fa-chart-line', 'Reports', 'Analytics and summaries at a glance', actions) }}
</div>
</div>

View File

@@ -4,19 +4,15 @@
{% block content %}
<div class="container-fluid">
{% from "_components.html" import page_header %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center">
<h1 class="h3 mb-0 me-3">
<i class="fas fa-clock text-primary"></i>
Log Time
</h1>
</div>
<a href="{{ url_for('main.dashboard') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i> Back
</a>
</div>
{% set actions %}
<a href="{{ url_for('main.dashboard') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-arrow-left"></i> Back
</a>
{% endset %}
{{ page_header('fas fa-clock', 'Log Time', 'Create a manual time entry', actions) }}
</div>
</div>