First commit
This commit is contained in:
198
templates/history.html
Normal file
198
templates/history.html
Normal 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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// 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>
|
||||
Reference in New Issue
Block a user