mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-08 04:30:20 -06:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
35
app/templates/_components.html
Normal file
35
app/templates/_components.html
Normal 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 %}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user