Initial commit: habit tracker PWA with Google OAuth, push notifications
FastAPI + SQLAlchemy/Alembic + MariaDB backend with Jinja2/htmx/Alpine server-rendered frontend. Multi-user via Google OAuth, daily habit tracking, monthly/weekly history views, Web Push reminders via APScheduler, and PWA support (manifest, service worker, offline caching). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}기록 · 습관 트래커{% endblock %}
|
||||
{% block nav_history %}active{% endblock %}
|
||||
{% block shell_class %} wide{% endblock %}
|
||||
{% block content %}
|
||||
<h1>기록</h1>
|
||||
|
||||
<div class="tabs">
|
||||
<a href="/history?view=month" class="{{ 'active' if view == 'month' }}">월별</a>
|
||||
<a href="/history?view=week" class="{{ 'active' if view == 'week' }}">주별</a>
|
||||
</div>
|
||||
|
||||
{% if view == "month" %}
|
||||
<div class="card">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-2);">
|
||||
<a href="/history?view=month&year={{ prev_year }}&month={{ prev_month }}" class="btn btn-secondary">‹</a>
|
||||
<h2 style="margin:0;">{{ year }}년 {{ month }}월</h2>
|
||||
<a href="/history?view=month&year={{ next_year }}&month={{ next_month }}" class="btn btn-secondary">›</a>
|
||||
</div>
|
||||
<div style="text-align:center; margin-bottom: var(--space-2);">
|
||||
<span class="badge">이번 달 완료율 {{ completion_rate }}%</span>
|
||||
</div>
|
||||
|
||||
<div class="calendar-grid calendar-grid-header">
|
||||
{% for wd in ["일", "월", "화", "수", "목", "금", "토"] %}
|
||||
<div class="calendar-weekday">{{ wd }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% for week in weeks %}
|
||||
<div class="calendar-grid">
|
||||
{% for d in week %}
|
||||
{% set summary = summary_map.get(d) %}
|
||||
<div
|
||||
class="calendar-cell{{ '' if d.month == month else ' muted' }}"
|
||||
style="background: rgba(var(--color-accent-rgb), {{ heatmap_opacity(summary.checked_count, summary.scheduled_count) if summary else 0 }});"
|
||||
>
|
||||
<span class="calendar-date">{{ d.day }}</span>
|
||||
{% if summary and summary.scheduled_count %}
|
||||
<span class="calendar-ratio">{{ summary.checked_count }}/{{ summary.scheduled_count }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="card" style="overflow-x:auto;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-2);">
|
||||
<a href="/history?view=week&start={{ prev_week }}" class="btn btn-secondary">‹</a>
|
||||
<h2 style="margin:0;">{{ week_start.strftime("%Y.%m.%d") }} - {{ week_end.strftime("%m.%d") }}</h2>
|
||||
<a href="/history?view=week&start={{ next_week }}" class="btn btn-secondary">›</a>
|
||||
</div>
|
||||
|
||||
{% if rows %}
|
||||
<table class="week-matrix">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>습관</th>
|
||||
{% for wd in ["일", "월", "화", "수", "목", "금", "토"] %}
|
||||
<th>{{ wd }}</th>
|
||||
{% endfor %}
|
||||
<th>완료율</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ row.name }}
|
||||
<span class="badge">{{ "형성" if row.habit_type == "build" else "중단" }}</span>
|
||||
</td>
|
||||
{% for iso_date, checked in row.checks.items() %}
|
||||
<td class="week-matrix-cell">
|
||||
{% if checked is none %}<span class="wm-dash">–</span>
|
||||
{% elif checked %}<span class="wm-check">✓</span>
|
||||
{% else %}<span class="wm-empty"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="week-matrix-cell">{{ row.completion_rate }}%</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-state">진행 중인 습관이 없어요.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user