- Reduce capture-item, files table, date group labels to 6px 12px padding - Set font-size 0.80rem on capture-text, file table cells Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
6.4 KiB
HTML
156 lines
6.4 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<div>
|
|
<h1 class="page-title">Time Log</h1>
|
|
<div class="text-muted text-sm mt-1">
|
|
{{ entries | length }} entries
|
|
· {{ (total_minutes / 60) | round(1) }}h total
|
|
(last {{ days }} days)
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Running timer banner -->
|
|
{% if running %}
|
|
<div class="card mb-3" style="border-color: var(--green); background: var(--green-soft);">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<div style="width: 10px; height: 10px; border-radius: 50%; background: var(--green); animation: pulse 1.5s infinite;"></div>
|
|
<div style="flex: 1;">
|
|
<div style="font-weight: 600; color: var(--text);">
|
|
Timer running: {{ running.task_title }}
|
|
</div>
|
|
{% if running.project_name %}
|
|
<div class="text-muted text-sm">{{ running.project_name }}</div>
|
|
{% endif %}
|
|
</div>
|
|
<div style="font-size: 1.25rem; font-weight: 700; font-family: var(--font-mono); color: var(--green);"
|
|
id="running-banner-elapsed" data-start="{{ running.start_at.isoformat() }}">
|
|
--:--
|
|
</div>
|
|
<form method="POST" action="/time/stop">
|
|
<input type="hidden" name="entry_id" value="{{ running.id }}">
|
|
<button type="submit" class="btn btn-danger btn-sm">Stop</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Filters -->
|
|
<div class="filters-bar">
|
|
<a href="/time?days=1" class="btn {{ 'btn-primary' if days == 1 else 'btn-secondary' }} btn-sm">Today</a>
|
|
<a href="/time?days=7" class="btn {{ 'btn-primary' if days == 7 else 'btn-secondary' }} btn-sm">7 Days</a>
|
|
<a href="/time?days=30" class="btn {{ 'btn-primary' if days == 30 else 'btn-secondary' }} btn-sm">30 Days</a>
|
|
<a href="/time?days=90" class="btn {{ 'btn-primary' if days == 90 else 'btn-secondary' }} btn-sm">90 Days</a>
|
|
</div>
|
|
|
|
<!-- Daily totals summary -->
|
|
{% if daily_totals %}
|
|
<div class="card mb-3">
|
|
<div class="card-title mb-2">Daily Summary</div>
|
|
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
|
|
{% for day, mins in daily_totals | dictsort(reverse=true) %}
|
|
<div style="text-align: center; padding: 8px 14px; background: var(--surface2); border-radius: var(--radius); min-width: 80px;">
|
|
<div style="font-size: 1.1rem; font-weight: 700;">{{ (mins / 60) | round(1) }}h</div>
|
|
<div class="text-muted text-xs">{{ day }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Time entries -->
|
|
{% if entries %}
|
|
<div class="card">
|
|
{% set current_date = namespace(value='') %}
|
|
{% for entry in entries %}
|
|
{% set entry_date = entry.start_at.strftime('%A, %B %-d') if entry.start_at else 'Unknown' %}
|
|
{% if entry_date != current_date.value %}
|
|
<div style="padding: 6px 12px 4px; font-size: 0.78rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; {% if not loop.first %}border-top: 1px solid var(--border); margin-top: 4px;{% endif %}">
|
|
{{ entry_date }}
|
|
</div>
|
|
{% set current_date.value = entry_date %}
|
|
{% endif %}
|
|
|
|
<div class="list-row">
|
|
{% if entry.end_at is none %}
|
|
<div style="width: 10px; height: 10px; border-radius: 50%; background: var(--green); animation: pulse 1.5s infinite; flex-shrink: 0;"></div>
|
|
{% endif %}
|
|
<div class="row-title">
|
|
<a href="/tasks/{{ entry.task_id }}">{{ entry.task_title }}</a>
|
|
</div>
|
|
{% if entry.project_name %}
|
|
<span class="row-tag">{{ entry.project_name }}</span>
|
|
{% endif %}
|
|
{% if entry.domain_name %}
|
|
<span class="row-domain-tag" style="background: {{ entry.domain_color or 'var(--accent)' }}20; color: {{ entry.domain_color or 'var(--accent)' }};">
|
|
{{ entry.domain_name }}
|
|
</span>
|
|
{% endif %}
|
|
<span style="font-family: var(--font-mono); font-size: 0.82rem; color: var(--text-secondary); min-width: 50px; text-align: right;">
|
|
{% if entry.end_at is none %}
|
|
<span style="color: var(--green);">running</span>
|
|
{% elif entry.duration_minutes %}
|
|
{% if entry.duration_minutes >= 60 %}
|
|
{{ (entry.duration_minutes / 60) | int }}h {{ entry.duration_minutes % 60 }}m
|
|
{% else %}
|
|
{{ entry.duration_minutes }}m
|
|
{% endif %}
|
|
{% else %}
|
|
--
|
|
{% endif %}
|
|
</span>
|
|
<span class="row-meta" style="min-width: 100px; text-align: right;">
|
|
{% if entry.start_at %}
|
|
{{ entry.start_at.strftime('%-I:%M %p') }}
|
|
{% if entry.end_at %}
|
|
- {{ entry.end_at.strftime('%-I:%M %p') }}
|
|
{% endif %}
|
|
{% endif %}
|
|
</span>
|
|
<div class="row-actions">
|
|
<form method="POST" action="/time/{{ entry.id }}/delete" data-confirm="Delete this time entry?">
|
|
<button type="submit" class="btn btn-ghost btn-xs" style="color: var(--red);">Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">⏱</div>
|
|
<div class="empty-state-text">No time entries in the last {{ days }} days</div>
|
|
<div class="text-muted text-sm">Start a timer from any task to begin tracking time</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<style>
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.4; }
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Update running banner elapsed time
|
|
(function() {
|
|
const el = document.getElementById('running-banner-elapsed');
|
|
if (!el) return;
|
|
const startAt = new Date(el.dataset.start);
|
|
function update() {
|
|
const now = new Date();
|
|
const secs = Math.floor((now - startAt) / 1000);
|
|
const h = Math.floor(secs / 3600);
|
|
const m = Math.floor((secs % 3600) / 60);
|
|
const s = secs % 60;
|
|
el.textContent = (h > 0 ? h + ':' : '') +
|
|
String(m).padStart(2, '0') + ':' +
|
|
String(s).padStart(2, '0');
|
|
}
|
|
update();
|
|
setInterval(update, 1000);
|
|
})();
|
|
</script>
|
|
{% endblock %}
|