First commit

This commit is contained in:
2026-04-12 18:55:00 +03:00
commit 8f1bf14191
12 changed files with 1451 additions and 0 deletions

198
templates/history.html Normal file
View File

@@ -0,0 +1,198 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>History — Daily Check-in</title>
<link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/theme.js"></script>
</head>
<body>
<nav class="topbar">
<span class="topbar-title">Daily Check-in</span>
<div class="topbar-nav">
<a href="/">Form</a>
<a href="/history" class="active">History</a>
</div>
<button class="theme-toggle" id="theme-toggle" title="Toggle theme"></button>
</nav>
<div class="page-wide">
<div class="cal-header">
<button class="cal-nav-btn" id="prev-btn"></button>
<h2 id="month-label"></h2>
<button class="cal-nav-btn" id="next-btn"></button>
</div>
<div class="cal-grid" id="cal-grid"></div>
</div>
<!-- Overlay -->
<div class="overlay" id="overlay" onclick="closePanel()"></div>
<!-- Snapshot panel -->
<div class="snapshot-panel" id="snapshot-panel">
<div class="snapshot-panel-header">
<span class="snapshot-panel-title" id="panel-title"></span>
<button class="panel-close-btn" onclick="closePanel()"></button>
</div>
<div class="snapshot-panel-body" id="panel-body">
<p style="color:var(--text3);font-size:13px">Select a day to view answers.</p>
</div>
</div>
<script>
const QUESTIONS = {{ questions | tojson }};
let snapshots = {}; // date string → snapshot doc
let curYear, curMonth;
const DAYS = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
async function loadSnapshots() {
const res = await fetch('/api/snapshots?limit=365');
const list = await res.json();
snapshots = {};
list.forEach(s => { snapshots[s.date] = s; });
renderCalendar();
}
function renderCalendar() {
const label = document.getElementById('month-label');
label.textContent = MONTHS[curMonth] + ' ' + curYear;
const grid = document.getElementById('cal-grid');
grid.innerHTML = '';
// Day of week headers
DAYS.forEach(d => {
const el = document.createElement('div');
el.className = 'cal-dow';
el.textContent = d;
grid.appendChild(el);
});
const today = new Date();
const todayStr = fmtDate(today);
// First day of month — Monday-based offset
const firstDay = new Date(curYear, curMonth, 1);
let offset = firstDay.getDay(); // 0=Sun
offset = offset === 0 ? 6 : offset - 1; // convert to Mon=0
for (let i = 0; i < offset; i++) {
const el = document.createElement('div');
el.className = 'cal-cell empty';
grid.appendChild(el);
}
const daysInMonth = new Date(curYear, curMonth + 1, 0).getDate();
for (let d = 1; d <= daysInMonth; d++) {
const dateStr = `${curYear}-${String(curMonth+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
const snap = snapshots[dateStr];
const isToday = dateStr === todayStr;
const isDone = snap && snap.answers && snap.answers.__done__;
const hasSnap = !!snap;
const el = document.createElement('div');
el.className = 'cal-cell' +
(hasSnap ? ' has-snapshot' : '') +
(isDone ? ' done' : '') +
(isToday ? ' today' : '');
el.innerHTML = `<span class="cal-day-num">${d}</span>`;
if (hasSnap && !isDone) el.innerHTML += `<span class="cal-dot"></span>`;
if (isDone) el.innerHTML += `<span class="cal-done-dot"></span>`;
if (hasSnap) el.addEventListener('click', () => openPanel(dateStr, snap));
grid.appendChild(el);
}
}
function fmtDate(d) {
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}
function openPanel(dateStr, snap) {
document.getElementById('panel-title').textContent = formatDateNice(dateStr);
const body = document.getElementById('panel-body');
// Download button
let html = `<a class="download-btn" href="/api/snapshots/${dateStr}/md" download="checkin-${dateStr}.md">⬇ Download .md</a>`;
// Done badge
if (snap.answers && snap.answers.__done__) {
const at = snap.answers.__done_at__ ? new Date(snap.answers.__done_at__).toLocaleTimeString('en-GB', {hour:'2-digit',minute:'2-digit'}) : '';
html += `<div style="display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:var(--success-bg);border:1px solid var(--success);border-radius:100px;font-size:12px;color:var(--success);margin-bottom:20px;margin-left:8px;">✓ Filled ${at ? 'at ' + at : ''}</div>`;
}
html += `<div style="border-top:1px solid var(--border);padding-top:20px;">`;
QUESTIONS.forEach(q => {
const val = snap.answers ? snap.answers[q.id] : null;
html += `<div class="snapshot-qa">
<div class="snapshot-q">${escHtml(q.label)}</div>
<div class="snapshot-a">${formatVal(q, val)}</div>
</div>`;
});
html += `</div>`;
body.innerHTML = html;
document.getElementById('snapshot-panel').classList.add('open');
document.getElementById('overlay').classList.add('visible');
}
function closePanel() {
document.getElementById('snapshot-panel').classList.remove('open');
document.getElementById('overlay').classList.remove('visible');
}
function formatVal(q, val) {
if (val == null) return `<span style="color:var(--text3)">—</span>`;
switch(q.type) {
case 'yesno': return val ? '✅ Yes' : '❌ No';
case 'duration': {
const h = Math.floor(val/60), m = val%60;
return `${h}h ${String(m).padStart(2,'0')}m`;
}
case 'time': {
const h = Math.floor(val/60), m = val%60;
return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`;
}
case 'multichoice': return Array.isArray(val) ? val.join(', ') : String(val);
default: return escHtml(String(val));
}
}
function formatDateNice(dateStr) {
const d = new Date(dateStr + 'T12:00:00');
return d.toLocaleDateString('en-GB', {weekday:'long', day:'numeric', month:'long', year:'numeric'});
}
function escHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// Nav
const now = new Date();
curYear = now.getFullYear();
curMonth = now.getMonth();
document.getElementById('prev-btn').addEventListener('click', () => {
curMonth--;
if (curMonth < 0) { curMonth = 11; curYear--; }
renderCalendar();
});
document.getElementById('next-btn').addEventListener('click', () => {
curMonth++;
if (curMonth > 11) { curMonth = 0; curYear++; }
renderCalendar();
});
loadSnapshots();
initThemeToggle();
</script>
</body>
</html>