Skip to content
39 changes: 23 additions & 16 deletions js/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const STORAGE_SAVE_KEY = "launchdesk-v1-items";
const STORAGE_LOAD_KEY = "launchdesk-items-v1"; // Intentional bug: this key should match STORAGE_SAVE_KEY.
const STORAGE_KEY = "launchdesk-v1-items";

const demoChecks = [
{
Expand Down Expand Up @@ -91,7 +90,7 @@ const activityLog = document.getElementById("activityLog");
let checks = loadChecks();
let currentView = checks;

form.addEventListener("submit", (event) => handleAddChek(event)); // Intentional bug: misspelled function name.
form.addEventListener("submit", (event) => handleAddCheck(event)); // Intentional bug: misspelled function name.
searchInput.addEventListener("input", applyFilters);
statusFilter.addEventListener("change", applyFilters);
priorityFilter.addEventListener("change", applyFilters);
Expand All @@ -104,7 +103,7 @@ renderApp();
logActivity("Demo data loaded. Start by testing the checklist workflows.");

function loadChecks() {
const saved = localStorage.getItem(STORAGE_LOAD_KEY);
const saved = localStorage.getItem(STORAGE_KEY);

if (!saved) {
return [...demoChecks];
Expand All @@ -119,7 +118,7 @@ function loadChecks() {
}

function saveChecks() {
localStorage.setItem(STORAGE_SAVE_KEY, JSON.stringify(checks));
localStorage.setItem(STORAGE_KEY, JSON.stringify(checks));
}

function handleAddCheck(event) {
Expand All @@ -132,7 +131,7 @@ function handleAddCheck(event) {
const owner = ownerInput.value.trim() || "Unassigned";
const dueDate = dueDateInput.value || new Date().toISOString().slice(0, 10);

if (!title && !category) {
if (!title || !category) {
// Intentional bug: validation should stop when either required field is missing.
formMessage.textContent =
"Please enter a check title and choose a category.";
Expand Down Expand Up @@ -164,11 +163,15 @@ function applyFilters() {
const selectedPriority = priorityFilter.value;

let filtered = checks.filter((check) =>
check.owner.toLowerCase().includes(searchTerm),
check.title.toLowerCase().includes(searchTerm) ||
check.category.toLowerCase().includes(searchTerm) ||
check.priority.toLowerCase().includes(searchTerm) ||
check.status.toLowerCase().includes(searchTerm) ||
check.owner.toLowerCase().includes(searchTerm)
); // Intentional bug: search should include title, category, priority, status, and owner.

if (selectedStatus !== "All") {
filtered = filtered.filter((check) => check.priority === selectedStatus);
filtered = filtered.filter((check) => check.status === selectedStatus);
} // Intentional bug: status filter compares against priority.

if (selectedPriority !== "All") {
Expand All @@ -191,7 +194,7 @@ function renderRows(list) {

const rows = list.map((check) => {
const priorityClass = `priority-${check.priority.toLowerCase()}`;
const statusClass = `status-${check.status.toLowerCase()}`; // Intentional bug: "In Progress" needs a slug class.
const statusClass = `status-${check.status.toLowerCase().replaceAll(" ", "-")}`; // Intentional bug: "In Progress" needs a slug class.

return `
<tr>
Expand Down Expand Up @@ -231,11 +234,14 @@ function renderRows(list) {

function updateMetrics() {
const total = checks.length;
const fixed = checks.filter((check) => check.status === "Complete").length; // Intentional bug: valid fixed status is "Fixed".
const fixed = checks.filter((check) => check.status === "Fixed").length; // Intentional bug: valid fixed status is "Fixed".
const criticalOpen = checks.filter(
(check) => check.priority === "Critical" && check.status !== "Fixed",
).length;
const dueSoon = checks.filter((check) => daysUntil(check.dueDate) > 7).length; // Intentional bug: this should count items due within 7 days.
const dueSoon = checks.filter((check) => {
const days = daysUntil(check.dueDate);
return days >= 0 && days <= 7;
}).length; // Intentional bug: this should count items due within 7 days.
const score = total === 0 ? 0 : Math.round((fixed / total) * 100);

totalCount.textContent = total;
Expand All @@ -247,13 +253,13 @@ function updateMetrics() {
}

function handleTableClick(event) {
const deleteButton = event.target.closest("[data-delete-id]"); // Intentional bug: button uses data-remove-id.
const deleteButton = event.target.closest("[data-remove-id]"); // Intentional bug: button uses data-remove-id.

if (!deleteButton) {
return;
}

const id = Number(deleteButton.dataset.deleteId);
const id = Number(deleteButton.dataset.removeId);
const removed = checks.find((check) => check.id === id);
checks = checks.filter((check) => check.id !== id);
saveChecks();
Expand All @@ -276,7 +282,8 @@ function handleStatusChange(event) {
}

check.status = statusSelect.value;
renderRows(currentView);
saveChecks();
applyFilters();
logActivity(`Changed "${check.title}" to ${check.status}.`);
// Intentional bug: status changes should save, update filters, and refresh metrics.
}
Expand All @@ -285,7 +292,7 @@ async function resetDemoData() {
formMessage.textContent = "";

try {
const response = await fetch("data/launch-seed.json"); // Intentional bug: real file is data/launch-checks.json.
const response = await fetch("data/launch-checks.json"); // Intentional bug: real file is data/launch-checks.json.

if (!response.ok) {
throw new Error(`Demo data request failed with ${response.status}`);
Expand All @@ -312,7 +319,7 @@ function exportCsv() {
"Due Date",
];
const rows = currentView.map((check) => [
check.name, // Intentional bug: property should be check.title.
check.title, // Intentional bug: property should be check.title.
check.category,
check.priority,
check.status,
Expand Down