Highlight seasonal Community Center priorities
This commit is contained in:
parent
8d5a94d7fa
commit
d2c7f004ea
8 changed files with 199 additions and 16 deletions
|
|
@ -1,6 +1,7 @@
|
|||
(() => {
|
||||
const data = window.VALLEY_DATA;
|
||||
const sync = window.ValleySync;
|
||||
const availability = window.ValleyAvailability;
|
||||
const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
||||
const seasonNames = { spring: "Spring", summer: "Summer", fall: "Fall", winter: "Winter" };
|
||||
const state = {
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
renderDate();
|
||||
renderToday();
|
||||
renderCrops();
|
||||
if (state.filter === "season") renderBundles();
|
||||
renderBundles();
|
||||
}
|
||||
|
||||
function moveDay(delta) {
|
||||
|
|
@ -149,19 +150,60 @@
|
|||
}, totals), { done: 0, required: 0 });
|
||||
}
|
||||
|
||||
function entryStatus(entry, group) {
|
||||
const progress = groupProgress(group);
|
||||
return availability.classify(entry, {
|
||||
checked: Boolean(state.checks[entry.id]),
|
||||
chooseGroupComplete: Boolean(group.choose && progress.complete),
|
||||
season: state.season
|
||||
});
|
||||
}
|
||||
|
||||
function allEntryStatuses() {
|
||||
return data.bundles.flatMap(room => room.groups.flatMap(group =>
|
||||
group.items.map(entry => ({ entry, group, status: entryStatus(entry, group) }))
|
||||
));
|
||||
}
|
||||
|
||||
function renderPrioritySummary() {
|
||||
const entries = allEntryStatuses();
|
||||
const count = filter => entries.filter(({ status }) => availability.matchesFilter(status, filter)).length;
|
||||
$("#needed-now-count").textContent = count("now");
|
||||
$("#exclusive-count").textContent = count("exclusive");
|
||||
$("#weather-count").textContent = count("weather");
|
||||
$("#needed-now-detail").textContent = `Available in ${seasonNames[state.season]}`;
|
||||
$("#exclusive-detail").textContent = `Only in ${seasonNames[state.season]}`;
|
||||
}
|
||||
|
||||
function seasonList(seasons) {
|
||||
return seasons.map(season => seasonNames[season]).join(" + ");
|
||||
}
|
||||
|
||||
function statusBadges(status) {
|
||||
if (status.checked) return "";
|
||||
const badges = [];
|
||||
if (!status.needed) badges.push(`<span class="status-badge optional">Bundle ready</span>`);
|
||||
else if (status.seasonExclusive) badges.push(`<span class="status-badge exclusive">Only ${seasonNames[state.season]}</span>`);
|
||||
else if (status.inSeason) badges.push(`<span class="status-badge in-season">In season</span>`);
|
||||
else if (status.seasonal) badges.push(`<span class="status-badge later">${seasonList(status.seasons)}</span>`);
|
||||
if (status.weather) badges.push(`<span class="status-badge weather ${status.weather.toLowerCase()}">${status.weather === "Rain" ? "Rain needed" : "Sunny only"}</span>`);
|
||||
return badges.length ? `<span class="item-badges">${badges.join("")}</span>` : "";
|
||||
}
|
||||
|
||||
function renderBundles() {
|
||||
const html = data.bundles.map(room => {
|
||||
const groups = room.groups.map(group => {
|
||||
const progress = groupProgress(group);
|
||||
let items = group.items;
|
||||
if (state.filter === "season") items = items.filter(entry => !entry.seasons.length || entry.seasons.includes(state.season));
|
||||
if (state.filter === "open") items = items.filter(entry => !state.checks[entry.id]);
|
||||
if (state.filter !== "all") items = items.filter(entry => availability.matchesFilter(entryStatus(entry, group), state.filter));
|
||||
if (!items.length) return "";
|
||||
return `<article class="bundle-card ${progress.complete ? "complete" : ""}">
|
||||
<header><div><h4>${escapeHtml(group.name)}</h4>${group.choose ? `<small>Choose ${group.required}</small>` : ""}</div><span>${Math.min(progress.done, progress.required)}/${progress.required}</span></header>
|
||||
<div class="bundle-items">${items.map(entry => {
|
||||
const checked = Boolean(state.checks[entry.id]);
|
||||
return `<label class="bundle-item ${checked ? "complete" : ""}"><input type="checkbox" data-check-id="${entry.id}" ${checked ? "checked" : ""} ${state.saving.has(entry.id) ? "disabled" : ""}><span class="checkmark" aria-hidden="true"></span><span><strong>${escapeHtml(entry.label)}</strong><small>${escapeHtml(entry.meta)}</small></span></label>`;
|
||||
const status = entryStatus(entry, group);
|
||||
const classes = [checked ? "complete" : "", status.needed ? "needed" : "optional", status.needed && status.availableNow ? "available-now" : "", status.seasonExclusive ? "season-exclusive" : "", status.weather ? "weather-specific" : ""].filter(Boolean).join(" ");
|
||||
return `<label class="bundle-item ${classes}"><input type="checkbox" data-check-id="${entry.id}" ${checked ? "checked" : ""} ${state.saving.has(entry.id) ? "disabled" : ""}><span class="checkmark" aria-hidden="true"></span><span class="item-copy"><strong>${escapeHtml(entry.label)}</strong><small>${escapeHtml(entry.meta)}</small>${statusBadges(status)}</span></label>`;
|
||||
}).join("")}</div>
|
||||
</article>`;
|
||||
}).filter(Boolean).join("");
|
||||
|
|
@ -170,7 +212,16 @@
|
|||
const roomDone = roomGroups.filter(progress => progress.complete).length;
|
||||
return `<section class="room"><div class="room-title"><span>${room.icon}</span><div><h3>${escapeHtml(room.room)}</h3><p>${roomDone} of ${room.groups.length} bundles ready</p></div></div><div class="room-grid">${groups}</div></section>`;
|
||||
}).filter(Boolean).join("");
|
||||
$("#bundle-list").innerHTML = html || `<div class="empty-state"><span>✓</span><h3>Everything in this view is done</h3><p>Switch filters to review the whole Community Center.</p></div>`;
|
||||
const emptyMessages = {
|
||||
now: ["Nothing needed right now", `No unfinished bundle items are available from normal sources in ${seasonNames[state.season]}.`],
|
||||
exclusive: ["No season-only items left", `You have covered every unfinished item exclusive to ${seasonNames[state.season]}.`],
|
||||
weather: ["No weather windows left", `No unfinished fish in ${seasonNames[state.season]} require rain or sun.`],
|
||||
open: ["Community Center covered", "No required bundle items remain."],
|
||||
all: ["Everything in this view is done", "Switch filters to review the whole Community Center."]
|
||||
};
|
||||
const empty = emptyMessages[state.filter] || emptyMessages.all;
|
||||
$("#bundle-list").innerHTML = html || `<div class="empty-state"><span>✓</span><h3>${empty[0]}</h3><p>${empty[1]}</p></div>`;
|
||||
renderPrioritySummary();
|
||||
const totals = bundleTotals();
|
||||
const percent = totals.required ? Math.round(totals.done / totals.required * 100) : 0;
|
||||
$("#progress-label").textContent = `${totals.done} of ${totals.required} essentials`;
|
||||
|
|
@ -282,7 +333,7 @@
|
|||
const filterButton = event.target.closest("[data-filter]");
|
||||
if (filterButton) {
|
||||
state.filter = filterButton.dataset.filter;
|
||||
$$("[data-filter]").forEach(button => button.setAttribute("aria-pressed", String(button === filterButton)));
|
||||
$$("[data-filter]").forEach(button => button.setAttribute("aria-pressed", String(button.dataset.filter === state.filter)));
|
||||
renderBundles();
|
||||
}
|
||||
const navLink = event.target.closest(".section-nav a");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue