updating modes and structures
This commit is contained in:
166
assets/scripts/wird-tracker.js
Normal file → Executable file
166
assets/scripts/wird-tracker.js
Normal file → Executable 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;
|
||||
|
||||
Reference in New Issue
Block a user