Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -560,3 +560,14 @@ tbody tr:last-child td {
font-size: 16px;
}
}

/* Improvement: overdue and due-soon date highlighting */
.overdue {
color: #dc2626;
font-weight: bold;
}

.due-soon {
color: #d97706;
font-weight: bold;
}
47 changes: 28 additions & 19 deletions js/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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_LOAD_KEY = "launchdesk-v1-items"; // Fixed: key now matches STORAGE_SAVE_KEY so saved data loads correctly

const demoChecks = [
{
Expand Down Expand Up @@ -91,7 +91,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)); // Fixed: corrected misspelled function name from handleAddChek
searchInput.addEventListener("input", applyFilters);
statusFilter.addEventListener("change", applyFilters);
priorityFilter.addEventListener("change", applyFilters);
Expand Down Expand Up @@ -132,8 +132,7 @@ function handleAddCheck(event) {
const owner = ownerInput.value.trim() || "Unassigned";
const dueDate = dueDateInput.value || new Date().toISOString().slice(0, 10);

if (!title && !category) {
// Intentional bug: validation should stop when either required field is missing.
if (!title || !category) { // Fixed: changed && to || so either missing field blocks form submission
formMessage.textContent =
"Please enter a check title and choose a category.";
return;
Expand Down Expand Up @@ -163,13 +162,18 @@ function applyFilters() {
const selectedStatus = statusFilter.value;
const selectedPriority = priorityFilter.value;

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

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

if (selectedPriority !== "All") {
filtered = filtered.filter((check) => check.priority === selectedPriority);
Expand All @@ -191,7 +195,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().replace(/\s+/g, '-')}`; // Fixed: added replace so "In Progress" becomes "in-progress" not "in progress"

return `
<tr>
Expand All @@ -205,7 +209,7 @@ function renderRows(list) {
<td><span class="priority-pill ${priorityClass}">${escapeHtml(check.priority)}</span></td>
<td><span class="status-badge ${statusClass}">${escapeHtml(check.status)}</span></td>
<td>${escapeHtml(check.owner)}</td>
<td>${formatDate(check.dueDate)}</td>
<td class="${daysUntil(check.dueDate) < 0 ? 'overdue' : daysUntil(check.dueDate) <= 3 ? 'due-soon' : ''}">${formatDate(check.dueDate)}</td>
<td>
<span class="row-actions">
<select data-status-id="${check.id}" aria-label="Update status for ${escapeHtml(check.title)}">
Expand All @@ -231,11 +235,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; // Fixed: was checking for "Complete" which is not a valid status value
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 <= 7 && days >= 0;
}).length; // Fixed: was counting items due after 7 days, now counts items due within 7 days
const score = total === 0 ? 0 : Math.round((fixed / total) * 100);

totalCount.textContent = total;
Expand All @@ -247,14 +254,16 @@ 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]"); // Fixed: was looking for data-delete-id but button uses data-remove-id

if (!deleteButton) {
return;
}

const id = Number(deleteButton.dataset.deleteId);
const id = Number(deleteButton.dataset.removeId); // Fixed: updated dataset property to match data-remove-id attribute
const removed = checks.find((check) => check.id === id);
const confirmed = confirm(`Delete "${removed?.title || 'this check'}"? This cannot be undone.`); // Improvement: ask user to confirm before deleting
if (!confirmed) return;
checks = checks.filter((check) => check.id !== id);
saveChecks();
applyFilters();
Expand All @@ -276,16 +285,16 @@ function handleStatusChange(event) {
}

check.status = statusSelect.value;
renderRows(currentView);
saveChecks(); // Fixed: added saveChecks so status change persists to localStorage
applyFilters(); // Fixed: added applyFilters to refresh metrics and filtered view after status change
logActivity(`Changed "${check.title}" to ${check.status}.`);
// Intentional bug: status changes should save, update filters, and refresh metrics.
}

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"); // Fixed: was fetching launch-seed.json which does not exist

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