Replace Progress Log section with a sidebar entry + calendar detail view
Progress Log is now one more selectable card in the same left-side list as Weekly Tasks and the 9 track cards, same level, same style -- no more ever-growing section stacked at the bottom of the page. Its detail view is a month calendar instead of a flat list: dates with logged activity are marked (pale green, count badge), and hovering a marked date reveals a popover with what was logged that day (track, source, text), scrollable past ~5 entries. Prev/next buttons navigate months, defaulting to the month of the most recent entry. Same computed aggregation as before (TASKS_DATA/WEEKLY_TASKS_DATA notes+reports), just a different view.
This commit is contained in:
1 parent
93725b50e3
commit
104a50cf15
1 file changed
+134
-32
+134
-32
@@ -723,6 +723,31 @@
|
||||
}
|
||||
.ab-card:hover { border-color: #2d6a9f; }
|
||||
.ab-card-weekly { background: #fffbe6; }
|
||||
|
||||
.pl-cal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
||||
.pl-cal-label { font-size: 1.05rem; font-weight: 800; color: #1b3a5c; }
|
||||
.pl-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
|
||||
.pl-cal-dow { font-size: 0.72rem; font-weight: 700; color: #6b7a8d; text-transform: uppercase; text-align: center; padding-bottom: 4px; }
|
||||
.pl-cal-day { position: relative; min-height: 64px; border: 1px solid #e0e7ef; border-radius: 6px; padding: 6px; }
|
||||
.pl-cal-day.empty { border-color: transparent; }
|
||||
.pl-cal-daynum { font-size: 0.85rem; font-weight: 700; color: #3d4c5c; }
|
||||
.pl-cal-day.has-entries { background: #eefbf1; border-color: #1a8a60; cursor: default; }
|
||||
.pl-cal-dot {
|
||||
position: absolute; top: 6px; right: 6px; background: #1a8a60; color: #fff;
|
||||
font-size: 0.68rem; font-weight: 800; border-radius: 50%;
|
||||
width: 18px; height: 18px; display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.pl-cal-popover {
|
||||
display: none; position: absolute; top: 100%; left: 0; z-index: 20;
|
||||
background: #fff; border: 1px solid #e0e7ef; border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.14); padding: 12px 14px; width: 280px;
|
||||
max-height: 320px; overflow-y: auto;
|
||||
font-size: 0.82rem; line-height: 1.55; color: #1a2533;
|
||||
}
|
||||
.pl-cal-day.has-entries:hover .pl-cal-popover { display: block; }
|
||||
.pl-cal-popover-date { font-weight: 800; color: #1b3a5c; margin-bottom: 6px; }
|
||||
.pl-cal-popover-entry { margin-bottom: 8px; }
|
||||
.pl-cal-popover-entry:last-child { margin-bottom: 0; }
|
||||
.ab-follow-star {
|
||||
position: absolute; bottom: 8px; right: 10px;
|
||||
font-size: 1rem; color: #eda100; line-height: 1;
|
||||
@@ -867,11 +892,6 @@
|
||||
<div class="ab-outreach-list" id="tasks-list"></div>
|
||||
<div class="ab-outreach-detail" id="tasks-detail"></div>
|
||||
</div>
|
||||
|
||||
<div style="background:#eefbf1; border-radius:10px; padding:24px; margin-top:24px;">
|
||||
<h2 style="margin:0 0 16px; font-size:1.3rem; font-weight:800; color:#1b3a5c;">Progress Log</h2>
|
||||
<div id="progress-log-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-address-book" class="tab-content">
|
||||
@@ -6491,10 +6511,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
var PROGRESS_LOG_ID = "progress-log-view";
|
||||
|
||||
function renderTasksBoard() {
|
||||
var allSelectable = TASKS_DATA.concat(WEEKLY_TASKS_DATA);
|
||||
if (!tasksSelectedId || !allSelectable.some(function (t) { return t.id === tasksSelectedId; })) {
|
||||
tasksSelectedId = TASKS_DATA.length ? TASKS_DATA[0].id : (WEEKLY_TASKS_DATA[0] && WEEKLY_TASKS_DATA[0].id);
|
||||
if (!tasksSelectedId || (tasksSelectedId !== PROGRESS_LOG_ID && !allSelectable.some(function (t) { return t.id === tasksSelectedId; }))) {
|
||||
tasksSelectedId = TASKS_DATA.length ? TASKS_DATA[0].id : (WEEKLY_TASKS_DATA[0] && WEEKLY_TASKS_DATA[0].id) || PROGRESS_LOG_ID;
|
||||
}
|
||||
|
||||
var list = document.getElementById("tasks-list");
|
||||
@@ -6515,9 +6537,16 @@
|
||||
list.appendChild(buildTaskSidebarCard(entry, entry.id === tasksSelectedId));
|
||||
});
|
||||
|
||||
list.appendChild(buildProgressLogSidebarCard(tasksSelectedId === PROGRESS_LOG_ID));
|
||||
|
||||
var detail = document.getElementById("tasks-detail");
|
||||
var selected = allSelectable.filter(function (t) { return t.id === tasksSelectedId; })[0];
|
||||
detail.innerHTML = selected ? buildTaskDetail(selected) : "<div class=\"ab-outreach-empty\">No tasks yet.</div>";
|
||||
if (tasksSelectedId === PROGRESS_LOG_ID) {
|
||||
detail.innerHTML = buildProgressLogCalendar();
|
||||
wireProgressLogCalendar();
|
||||
} else {
|
||||
var selected = allSelectable.filter(function (t) { return t.id === tasksSelectedId; })[0];
|
||||
detail.innerHTML = selected ? buildTaskDetail(selected) : "<div class=\"ab-outreach-empty\">No tasks yet.</div>";
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown toggle only -- doesn't itself become the selection, just expands/collapses
|
||||
@@ -6628,13 +6657,14 @@
|
||||
|
||||
renderTasksBoard();
|
||||
|
||||
/* ---- Progress Log: computed, not its own data store. Walks every TASKS_DATA/
|
||||
WEEKLY_TASKS_DATA entry's notes + reports (each already {date, source, text})
|
||||
and merges them into one chronological feed, newest first. This is the single
|
||||
source of truth for "what's been accomplished" -- there's no separate array to
|
||||
keep in sync, so a fact can never drift between two records. */
|
||||
function renderProgressLog() {
|
||||
var list = document.getElementById("progress-log-list");
|
||||
/* ---- Progress Log: a normal entry in the same sidebar list as Weekly Tasks and the
|
||||
track cards, not a separate section. Its detail view is a calendar (not a list)
|
||||
-- still computed from the exact same TASKS_DATA/WEEKLY_TASKS_DATA notes+reports,
|
||||
just displayed differently. Dates with logged activity are marked; hovering a
|
||||
marked date reveals what was logged that day. */
|
||||
var progressLogCalMonth = null; // "YYYY-MM", set to the most recent entry's month on first render
|
||||
|
||||
function getProgressLogEntries() {
|
||||
var allTracks = TASKS_DATA.concat(WEEKLY_TASKS_DATA);
|
||||
var entries = [];
|
||||
allTracks.forEach(function (track) {
|
||||
@@ -6642,24 +6672,96 @@
|
||||
(track.reports || []).forEach(function (r) { entries.push({ date: r.date, source: r.source, text: r.text, track: track.label }); });
|
||||
});
|
||||
entries.sort(function (a, b) { return b.date.localeCompare(a.date); });
|
||||
|
||||
if (!entries.length) {
|
||||
list.innerHTML = "<div class=\"ab-outreach-empty\">No entries yet.</div>";
|
||||
return;
|
||||
}
|
||||
list.innerHTML = entries.map(function (entry) {
|
||||
return (
|
||||
"<div class=\"card\">" +
|
||||
"<div style=\"font-size:0.78rem; font-weight:700; color:#6b7a8d; text-transform:uppercase; letter-spacing:0.04em; margin-bottom:6px;\">" +
|
||||
tasksEsc(entry.date) + " · " + tasksEsc(entry.track) + " · " + tasksEsc(entry.source) +
|
||||
"</div>" +
|
||||
"<p style=\"margin:0; font-size:0.95rem; line-height:1.65; color:#3d4c5c;\">" + tasksEsc(entry.text) + "</p>" +
|
||||
"</div>"
|
||||
);
|
||||
}).join("");
|
||||
return entries;
|
||||
}
|
||||
|
||||
renderProgressLog();
|
||||
function buildProgressLogSidebarCard(isSelected) {
|
||||
var card = document.createElement("div");
|
||||
card.className = "ab-card" + (isSelected ? " selected" : "");
|
||||
card.dataset.id = PROGRESS_LOG_ID;
|
||||
var entries = getProgressLogEntries();
|
||||
card.innerHTML =
|
||||
"<div class=\"ab-card-top\">" +
|
||||
"<div class=\"ab-card-company\">Progress Log</div>" +
|
||||
"<span class=\"ab-source-chip boss\">" + entries.length + (entries.length === 1 ? " entry" : " entries") + "</span>" +
|
||||
"</div>" +
|
||||
"<div class=\"ab-card-name\">Cross-track accomplishments, by date</div>";
|
||||
card.addEventListener("click", function () {
|
||||
tasksSelectedId = PROGRESS_LOG_ID;
|
||||
renderTasksBoard();
|
||||
});
|
||||
return card;
|
||||
}
|
||||
|
||||
function pad2(n) { return n < 10 ? "0" + n : String(n); }
|
||||
|
||||
function buildProgressLogCalendar() {
|
||||
var entries = getProgressLogEntries();
|
||||
var byDate = {};
|
||||
entries.forEach(function (e) { (byDate[e.date] = byDate[e.date] || []).push(e); });
|
||||
|
||||
if (!progressLogCalMonth) {
|
||||
progressLogCalMonth = entries.length ? entries[0].date.slice(0, 7) : "2026-08";
|
||||
}
|
||||
|
||||
var parts = progressLogCalMonth.split("-");
|
||||
var year = parseInt(parts[0], 10);
|
||||
var month = parseInt(parts[1], 10) - 1; // 0-indexed
|
||||
var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
var firstOfMonth = new Date(year, month, 1);
|
||||
var daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
var leadingBlanks = (firstOfMonth.getDay() + 6) % 7; // Monday-first week
|
||||
|
||||
var cells = "";
|
||||
for (var i = 0; i < leadingBlanks; i++) cells += "<div class=\"pl-cal-day empty\"></div>";
|
||||
for (var d = 1; d <= daysInMonth; d++) {
|
||||
var dateStr = year + "-" + pad2(month + 1) + "-" + pad2(d);
|
||||
var dayEntries = byDate[dateStr];
|
||||
if (dayEntries && dayEntries.length) {
|
||||
var popoverItems = dayEntries.map(function (e) {
|
||||
return "<div class=\"pl-cal-popover-entry\"><strong>" + tasksEsc(e.track) + "</strong> · " + tasksEsc(e.source) + "<br>" + tasksEsc(e.text) + "</div>";
|
||||
}).join("");
|
||||
cells +=
|
||||
"<div class=\"pl-cal-day has-entries\">" +
|
||||
"<div class=\"pl-cal-daynum\">" + d + "</div>" +
|
||||
"<div class=\"pl-cal-dot\">" + dayEntries.length + "</div>" +
|
||||
"<div class=\"pl-cal-popover\">" +
|
||||
"<div class=\"pl-cal-popover-date\">" + tasksEsc(dateStr) + "</div>" +
|
||||
popoverItems +
|
||||
"</div>" +
|
||||
"</div>";
|
||||
} else {
|
||||
cells += "<div class=\"pl-cal-day\"><div class=\"pl-cal-daynum\">" + d + "</div></div>";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
"<div class=\"card\">" +
|
||||
"<div class=\"pl-cal-header\">" +
|
||||
"<button type=\"button\" id=\"pl-cal-prev\" class=\"drill-back\">←</button>" +
|
||||
"<div class=\"pl-cal-label\">" + monthNames[month] + " " + year + "</div>" +
|
||||
"<button type=\"button\" id=\"pl-cal-next\" class=\"drill-back\">→</button>" +
|
||||
"</div>" +
|
||||
"<div class=\"pl-cal-grid\">" +
|
||||
"<div class=\"pl-cal-dow\">Mon</div><div class=\"pl-cal-dow\">Tue</div><div class=\"pl-cal-dow\">Wed</div><div class=\"pl-cal-dow\">Thu</div><div class=\"pl-cal-dow\">Fri</div><div class=\"pl-cal-dow\">Sat</div><div class=\"pl-cal-dow\">Sun</div>" +
|
||||
cells +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
|
||||
function wireProgressLogCalendar() {
|
||||
function shiftMonth(delta) {
|
||||
var parts = progressLogCalMonth.split("-");
|
||||
var d = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1 + delta, 1);
|
||||
progressLogCalMonth = d.getFullYear() + "-" + pad2(d.getMonth() + 1);
|
||||
renderTasksBoard();
|
||||
}
|
||||
var prev = document.getElementById("pl-cal-prev");
|
||||
var next = document.getElementById("pl-cal-next");
|
||||
if (prev) prev.addEventListener("click", function () { shiftMonth(-1); });
|
||||
if (next) next.addEventListener("click", function () { shiftMonth(1); });
|
||||
}
|
||||
|
||||
/* ---- Opportunities module loader ----
|
||||
Fetches its markup, injects it into the empty tab container, then loads its
|
||||
|
||||
Reference in new issue
Block a user