updating modes and structures

This commit is contained in:
2026-04-02 11:27:50 +01:00
parent 529acd4fcc
commit 172a1f85c3
297 changed files with 1328 additions and 3350 deletions

166
assets/scripts/wird-tracker.js Normal file → Executable file
View File

@@ -327,17 +327,18 @@
// ── Boot ─────────────────────────────────────────────────
document.addEventListener("DOMContentLoaded", async () => {
setTodayLabel();
await Promise.all([loadAll(), loadMotalah()]);
renderToday();
renderNafl();
renderMeetingPanel();
renderKhatm();
renderHistory();
renderChart();
renderMotalah();
renderHeatmap();
bindControls();
setTodayLabel();
await Promise.all([loadAll(), loadMotalah()]);
renderToday();
renderNafl();
renderMeetingPanel();
renderKhatm();
renderHistory();
renderChart();
renderMotalah();
renderSummary();
renderHeatmap();
bindControls();
});
function setTodayLabel() {
@@ -827,8 +828,9 @@
const HIDE_TYPES = new Set(["shaykh_meeting", ...Object.keys(NAFL_WIRD), "khatm"]);
const sorted = [...allEntries]
.filter(e => !HIDE_TYPES.has(e.wirdType))
.sort((a, b) => b.date.localeCompare(a.date));
.filter(e => !HIDE_TYPES.has(e.wirdType))
.sort((a, b) => b.date.localeCompare(a.date))
.slice(0, 20);
if (sorted.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="loading-cell">No entries yet.</td></tr>`;
@@ -1192,6 +1194,144 @@
return `${label}: ${Math.round(raw).toLocaleString()} ${meta.unit}`;
}
// ============================================================
// DAILY SUMMARY TABLE
// ============================================================
let summaryDays = 30;
let summaryFilter = "__all__";
function renderSummary() {
const wrap = document.getElementById("summary-table-wrap");
if (!wrap) return;
const today = isoDate();
const cols = summaryFilter === "__all__"
? Object.keys(DAILY_WIRD)
: [summaryFilter];
// Build date rows newest→oldest
const rows = [];
for (let i = 0; i < summaryDays; i++) {
const d = new Date();
d.setDate(d.getDate() - i);
rows.push(isoDate(d));
}
// Pre-aggregate entries into { date: { type: val } }
const agg = {};
for (const e of allEntries) {
if (!cols.includes(e.wirdType)) continue;
if (!agg[e.date]) agg[e.date] = {};
const meta = DAILY_WIRD[e.wirdType];
if (meta && meta.type === "rating") {
agg[e.date][e.wirdType] = Math.max(
agg[e.date][e.wirdType] || 0, Number(e.value)
);
} else {
agg[e.date][e.wirdType] = (agg[e.date][e.wirdType] || 0) + Number(e.value);
}
}
// Build header
const thCols = cols.map(t => {
const meta = DAILY_WIRD[t];
const unitHint = meta.type === "rating" ? "/5" : meta.unit ? meta.unit : "";
return `<th title="${meta.label} · target: ${meta.target}${unitHint}">
${meta.label}<br>
<span style="font-weight:400;opacity:.6">/ ${meta.target}${unitHint}</span>
</th>`;
}).join("");
// Build body rows
const bodyRows = rows.map(dateIso => {
const isToday = dateIso === today;
const dayData = agg[dateIso] || {};
const displayDate = new Date(dateIso + "T00:00:00").toLocaleDateString("en-GB", {
weekday: "short", day: "numeric", month: "short"
});
const cells = cols.map(type => {
const meta = DAILY_WIRD[type];
const val = dayData[type];
if (val === undefined || val === null) {
return `<td class="summary-cell-empty">—</td>`;
}
const cls = statusClass(type, val);
const cellCls = cls === "over" ? "summary-cell-over"
: cls === "done" ? "summary-cell-done"
: cls === "miss" ? "summary-cell-miss"
: "";
let display;
if (meta.type === "rating") {
display = `${val}<span style="opacity:.45">/5</span>`;
} else {
display = Number(val).toLocaleString();
}
return `<td class="${cellCls}">${display}</td>`;
}).join("");
return `<tr class="${isToday ? "summary-today" : ""}">
<td>${displayDate}${isToday ? " ·" : ""}</td>
${cells}
</tr>`;
}).join("");
// Footer: days target met per column
const footCells = cols.map(type => {
const meta = DAILY_WIRD[type];
let metCount = 0;
for (const dateIso of rows) {
const val = (agg[dateIso] || {})[type];
if (val !== undefined && val >= meta.target) metCount++;
}
const pct = Math.round((metCount / summaryDays) * 100);
const cls = pct >= 80 ? "summary-cell-done" : pct >= 50 ? "" : "summary-cell-miss";
return `<td class="summary-tfoot ${cls}">${metCount}d <span style="opacity:.55">(${pct}%)</span></td>`;
}).join("");
wrap.innerHTML = `
<table id="summary-table">
<thead>
<tr>
<th>Date</th>
${thCols}
</tr>
</thead>
<tbody>${bodyRows}</tbody>
<tfoot>
<tr>
<td class="summary-tfoot" style="color:var(--muted)">days met</td>
${footCells}
</tr>
</tfoot>
</table>
`;
}
// Controls
document.querySelectorAll(".summary-preset").forEach(btn => {
btn.addEventListener("click", () => {
summaryDays = parseInt(btn.dataset.days);
document.querySelectorAll(".summary-preset").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
renderSummary();
});
});
const summaryFilterEl = document.getElementById("summary-filter");
if (summaryFilterEl) {
summaryFilterEl.addEventListener("change", () => {
summaryFilter = summaryFilterEl.value;
renderSummary();
});
}
function renderHeatmapStats(scoreMap, type, startIso, endIso) {
const el = $("#heatmap-stats");
const daysLogged = Object.values(scoreMap).filter(v => v > 0).length;