feat: unified calendar view and eisenhower matrix view
This commit is contained in:
@@ -60,6 +60,10 @@
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
||||
Appointments
|
||||
</a>
|
||||
<a href="/calendar" class="nav-item {{ 'active' if active_nav == 'calendar' }}">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><rect x="7" y="14" width="3" height="3" rx="0.5"/><rect x="14" y="14" width="3" height="3" rx="0.5"/></svg>
|
||||
Calendar
|
||||
</a>
|
||||
<a href="/decisions" class="nav-item {{ 'active' if active_nav == 'decisions' }}">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="M9 12l2 2 4-4"/></svg>
|
||||
Decisions
|
||||
@@ -76,6 +80,10 @@
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
Time Log
|
||||
</a>
|
||||
<a href="/time-budgets" class="nav-item {{ 'active' if active_nav == 'time_budgets' }}">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/><path d="M16 3l2 2-2 2"/></svg>
|
||||
Time Budgets
|
||||
</a>
|
||||
<a href="/capture" class="nav-item {{ 'active' if active_nav == 'capture' }}">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
|
||||
Capture
|
||||
@@ -183,7 +191,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
||||
<span>Tasks</span>
|
||||
</a>
|
||||
<a href="/appointments" class="mobile-nav-item {% if active_nav == 'appointments' %}active{% endif %}">
|
||||
<a href="/calendar" class="mobile-nav-item {% if active_nav == 'calendar' %}active{% endif %}">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
||||
<span>Calendar</span>
|
||||
</a>
|
||||
|
||||
58
templates/calendar.html
Normal file
58
templates/calendar.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Calendar</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Month Navigation -->
|
||||
<div class="cal-nav">
|
||||
<a href="/calendar?year={{ prev_year }}&month={{ prev_month }}" class="btn btn-secondary btn-sm">← Prev</a>
|
||||
<span class="cal-month-label">{{ month_name }} {{ year }}</span>
|
||||
<a href="/calendar?year={{ next_year }}&month={{ next_month }}" class="btn btn-secondary btn-sm">Next →</a>
|
||||
{% if year != today.year or month != today.month %}
|
||||
<a href="/calendar" class="btn btn-ghost btn-sm">Today</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div class="cal-legend">
|
||||
<span class="cal-legend-item"><span class="cal-dot cal-dot-appointment"></span> Appointment</span>
|
||||
<span class="cal-legend-item"><span class="cal-dot cal-dot-meeting"></span> Meeting</span>
|
||||
<span class="cal-legend-item"><span class="cal-dot cal-dot-task"></span> Task</span>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Grid -->
|
||||
<div class="cal-grid">
|
||||
<div class="cal-header-row">
|
||||
<div class="cal-header-cell">Sun</div>
|
||||
<div class="cal-header-cell">Mon</div>
|
||||
<div class="cal-header-cell">Tue</div>
|
||||
<div class="cal-header-cell">Wed</div>
|
||||
<div class="cal-header-cell">Thu</div>
|
||||
<div class="cal-header-cell">Fri</div>
|
||||
<div class="cal-header-cell">Sat</div>
|
||||
</div>
|
||||
{% for week in weeks %}
|
||||
<div class="cal-week-row">
|
||||
{% for day in week %}
|
||||
<div class="cal-day-cell {{ 'cal-day-empty' if day == 0 }} {{ 'cal-day-today' if day > 0 and today.year == year and today.month == month and today.day == day }}">
|
||||
{% if day > 0 %}
|
||||
<div class="cal-day-num">{{ day }}</div>
|
||||
<div class="cal-events">
|
||||
{% for event in days_map.get(day, []) %}
|
||||
<a href="{{ event.url }}" class="cal-event cal-event-{{ event.type }}" title="{{ event.title }}">
|
||||
{% if event.time %}<span class="cal-event-time">{{ event.time }}</span>{% endif %}
|
||||
<span class="cal-event-title">{{ event.title }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
76
templates/time_budgets.html
Normal file
76
templates/time_budgets.html
Normal file
@@ -0,0 +1,76 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Time Budgets <span class="page-count">({{ count }})</span></h1>
|
||||
</div>
|
||||
<a href="/time-budgets/create" class="btn btn-primary">+ New Budget</a>
|
||||
</div>
|
||||
|
||||
{% if overcommitted %}
|
||||
<div class="alert alert-warning mb-3">
|
||||
<strong>Overcommitted!</strong> Your budgets total {{ "%.1f"|format(total_budgeted) }} hours/week, which exceeds the 168 hours available.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if current_budgets %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<span class="card-title">This Week's Budget vs Actual</span>
|
||||
<span class="text-muted text-sm">{{ "%.1f"|format(total_budgeted) }}h budgeted total</span>
|
||||
</div>
|
||||
|
||||
{% for b in current_budgets %}
|
||||
<div class="list-row" style="flex-wrap: wrap; gap: 8px;">
|
||||
<span class="domain-dot" style="background: {{ b.domain_color or '#4F6EF7' }}; flex-shrink: 0;"></span>
|
||||
<div class="row-title" style="min-width: 120px;">
|
||||
{{ b.domain_name }}
|
||||
</div>
|
||||
<div style="flex: 2; min-width: 200px; display: flex; align-items: center; gap: 8px;">
|
||||
<div class="progress-bar" style="flex: 1; height: 8px;">
|
||||
<div class="progress-fill" style="width: {{ [b.pct, 100] | min }}%; {{ 'background: var(--red);' if b.pct > 100 }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="row-meta" style="min-width: 100px; text-align: right;">
|
||||
<strong>{{ b.actual_hours }}h</strong> / {{ b.weekly_hours_float }}h
|
||||
</span>
|
||||
<span class="row-meta" style="min-width: 40px; text-align: right; {{ 'color: var(--red); font-weight: 600;' if b.pct > 100 else ('color: var(--green);' if b.pct >= 80 else '') }}">
|
||||
{{ b.pct }}%
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if all_budgets %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">All Budgets</span>
|
||||
</div>
|
||||
|
||||
{% for b in all_budgets %}
|
||||
<div class="list-row">
|
||||
<span class="domain-dot" style="background: {{ b.domain_color or '#4F6EF7' }}; flex-shrink: 0;"></span>
|
||||
<div class="row-title">
|
||||
{{ b.domain_name }}
|
||||
</div>
|
||||
<span class="row-meta">{{ b.weekly_hours }}h / week</span>
|
||||
<span class="row-meta">from {{ b.effective_from.strftime('%b %-d, %Y') if b.effective_from else '—' }}</span>
|
||||
<div class="row-actions">
|
||||
<a href="/time-budgets/{{ b.id }}/edit" class="btn btn-ghost btn-xs">Edit</a>
|
||||
<form method="POST" action="/time-budgets/{{ b.id }}/delete" data-confirm="Delete this budget?">
|
||||
<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 budgets defined</div>
|
||||
<a href="/time-budgets/create" class="btn btn-primary">Create a Budget</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
47
templates/time_budgets_form.html
Normal file
47
templates/time_budgets_form.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">{{ 'Edit Time Budget' if budget else 'New Time Budget' }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="max-width: 600px;">
|
||||
<form method="POST" action="{{ '/time-budgets/' ~ budget.id ~ '/edit' if budget else '/time-budgets/create' }}">
|
||||
<div class="form-grid" style="grid-template-columns: 1fr;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Domain *</label>
|
||||
<select name="domain_id" class="form-select" required>
|
||||
<option value="">Select domain...</option>
|
||||
{% for d in domains %}
|
||||
<option value="{{ d.id }}" {{ 'selected' if budget and budget.domain_id|string == d.id|string }}>
|
||||
{{ d.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Weekly Hours *</label>
|
||||
<input type="number" name="weekly_hours" class="form-input"
|
||||
value="{{ budget.weekly_hours if budget else '' }}"
|
||||
min="0" max="168" step="0.5" required
|
||||
placeholder="e.g. 10">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Effective From *</label>
|
||||
<input type="date" name="effective_from" class="form-input"
|
||||
value="{{ budget.effective_from.strftime('%Y-%m-%d') if budget and budget.effective_from else '' }}"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">{{ 'Save Changes' if budget else 'Create Budget' }}</button>
|
||||
<a href="/time-budgets" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user