LearningJavaScript TutorialMini Project
CHAPTER 18 · FINAL PRACTICAL PROJECT

Mini Project — Learning Progress Dashboard

Ab poore JavaScript course ke concepts ko ek real browser project me combine karte hain. Hum searchable, filterable aur interactive learning dashboard banayenge jisme arrays, objects, functions, DOM, events, state, async flow, accessibility aur debugging sab ek saath use honge.

English + Hinglish60+ min buildRunnable demo included

Run the finished project

Code padhne se pehle completed dashboard ko browser me use karke behavior observe karo.

Open Live Mini Project →

Project goal

We will build a small learning dashboard that shows course progress and lets a learner search, filter and update progress without reloading the page.

Hinglish Explanation

Is project ka focus flashy UI nahi, balki clean JavaScript architecture hai. Data ek array me rahega, UI us data se render hogi, user events state ko update karenge aur state change ke baad screen dubara render hogi.

Features we will build

  • Course cards rendered from JavaScript data.
  • Search by course title or category.
  • All, In Progress and Completed filters.
  • Mark complete / mark in progress action.
  • Total, in-progress, completed and average-progress stats.
  • Reset button for the demo state.
  • Accessible status updates with a polite live region.
  • Responsive layout for desktop and mobile.
  • Async initialization pattern with error handling.

Project file structure

structureFILES
mini-project/
├── index.html
├── style.css
└── app.js

HTML structure provide karta hai, CSS layout/design handle karta hai, aur JavaScript data + behavior manage karta hai.

1. Build the semantic HTML shell

Dashboard me native inputs and buttons use karo. Search ke liye input type="search", filters ke liye buttons, aur dynamic results ke liye empty section container rakho.

index.html ideaHTML
<label>
  <span>Search courses</span>
  <input id="course-search" type="search">
</label>

<div class="filters" role="group" aria-label="Filter courses">
  <button data-filter="all" aria-pressed="true">All</button>
  <button data-filter="progress" aria-pressed="false">In progress</button>
  <button data-filter="complete" aria-pressed="false">Completed</button>
</div>

<p id="dashboard-status" role="status" aria-live="polite"></p>
<section id="course-grid" aria-label="Courses"></section>
Accessibility: Native controls keyboard-friendly hote hain. aria-pressed filter button state communicate karta hai, aur status region result changes announce kar sakta hai.

2. Model course data with objects

Each course is an object and the full collection is an array.

app.jsJS
const COURSE_DATA = [
  { id: 1, title: "HTML Fundamentals", category: "Web Development", progress: 100 },
  { id: 2, title: "CSS Styling", category: "Web Development", progress: 82 },
  { id: 3, title: "JavaScript", category: "Programming", progress: 68 }
];

Stable id events me specific course identify karne ke kaam aata hai.

3. Keep UI state in one place

state.jsJS
const state = {
  courses: [],
  query: "",
  filter: "all"
};

This small state object makes it clear what can change during the session.

Project rule: DOM ko source of truth mat banao. Course data and filter state JavaScript objects me rakho; DOM un values ka visual output hai.

4. Initialize asynchronously

Real apps often load data from APIs. Demo local data use karta hai, but initialization Promise-based rakha gaya hai so async architecture practice ho.

load.jsJS
function loadCourses() {
  return Promise.resolve(
    COURSE_DATA.map(course => ({ ...course }))
  );
}

async function init() {
  try {
    status.textContent = "Loading courses…";
    state.courses = await loadCourses();
    renderCourses();
  } catch (error) {
    console.error("Dashboard failed to initialize", error);
    status.textContent = "Could not load the dashboard.";
  }
}

Later loadCourses() ko real fetch() function se replace karna easy hoga.

5. Derive visible courses

Search and filter original array ko mutate nahi karte. We derive a visible list with filter().

filter.jsJS
function getVisibleCourses() {
  const query = state.query.trim().toLowerCase();

  return state.courses.filter(course => {
    const matchesQuery =
      course.title.toLowerCase().includes(query) ||
      course.category.toLowerCase().includes(query);

    const matchesFilter =
      state.filter === "all" ||
      (state.filter === "complete" && course.progress === 100) ||
      (state.filter === "progress" && course.progress > 0 && course.progress < 100);

    return matchesQuery && matchesFilter;
  });
}
Why this is useful

Search aur filter ko separate DOM hacks se manage karne ke bajay ek function visible data calculate karta hai. Isse behavior predictable aur debug karna easier hota hai.

6. Render cards safely

API ya user-like data ko plain text ke roop me render karte waqt textContent use karo.

render.jsJS
function createCourseCard(course) {
  const card = document.createElement("article");
  card.className = "course-card";

  const title = document.createElement("h2");
  title.textContent = course.title;

  const button = document.createElement("button");
  button.type = "button";
  button.dataset.courseId = String(course.id);
  button.textContent = course.progress === 100
    ? "Mark in progress"
    : "Mark complete";

  card.append(title, button);
  return card;
}

Full demo adds category, progress label and progress bar too.

7. Replace the rendered list

render-list.jsJS
function renderCourses() {
  const courses = getVisibleCourses();
  grid.replaceChildren(...courses.map(createCourseCard));
  updateStats();

  status.textContent = courses.length
    ? `${courses.length} courses shown.`
    : "No courses match this search or filter.";
}

replaceChildren() old rendered cards ko clear karke current derived state render karta hai.

8. Calculate dashboard stats

stats.jsJS
const completed = state.courses.filter(
  course => course.progress === 100
).length;

const average = Math.round(
  state.courses.reduce((sum, course) => sum + course.progress, 0) /
  state.courses.length
);

Yahan arrays chapter ke filter() and reduce() real UI data ke saath use ho rahe hain.

9. Search with the input event

search.jsJS
searchInput.addEventListener("input", event => {
  state.query = event.target.value;
  renderCourses();
});

User type karta hai → state change hoti hai → render function current state ko UI me reflect karta hai.

10. Filter buttons

Filter group par one listener use karke event delegation apply kar sakte hain.

filters.jsJS
filters.addEventListener("click", event => {
  const button = event.target.closest("button[data-filter]");
  if (!button) return;

  state.filter = button.dataset.filter;
  renderCourses();
});

Finished project button state ko aria-pressed ke saath synchronize bhi karta hai.

11. Update one course

Course action buttons dynamic hain, so grid-level event delegation useful hai.

progress.jsJS
function toggleCourseProgress(courseId) {
  state.courses = state.courses.map(course => {
    if (course.id !== courseId) return course;

    return {
      ...course,
      progress: course.progress === 100 ? 50 : 100
    };
  });

  renderCourses();
}

Object spread old object ko mutate karne ke bajay updated object banata hai.

12. Event delegation on dynamic cards

delegation.jsJS
grid.addEventListener("click", event => {
  const button = event.target.closest("button[data-course-id]");
  if (!button) return;

  toggleCourseProgress(Number(button.dataset.courseId));
});
Why delegation? Cards rerender hote rehte hain, but parent grid stable hai. Har new button par listener dubara attach karna required nahi.

13. Reset demo state

Reset action starting dataset ka fresh shallow copy banata hai, search clear karta hai and filter ko All par return karta hai.

reset.jsJS
state.courses = COURSE_DATA.map(course => ({ ...course }));
state.query = "";
state.filter = "all";
renderCourses();

14. Responsive CSS

JavaScript project ka responsive behavior mostly CSS responsibility hai. Cards desktop par grid me, tablet par two columns and small screens par one column ho sakte hain.

style.css ideaCSS
.course-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

@media (max-width: 820px) {
  .course-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 560px) {
  .course-grid { grid-template-columns: 1fr; }
}

15. Accessibility review

  • Search input has a visible label.
  • Filter controls are real buttons.
  • Selected filter uses aria-pressed.
  • Status text uses role="status" and polite live updates.
  • Focus styles remain visible.
  • All actions work with keyboard activation.
  • Progress is also written as text, not shown by color alone.

16. Debug the project systematically

DevTools me three places inspect karo: Console for runtime errors, Elements for generated cards/attributes, aur Sources for breakpoints in filter/render functions.

Debug exercise: Intentionally course.progress === 100 ko course.progress = 100 bana kar bug observe karo, then console/breakpoint se reason find karke fix karo.

Project data flow

mental modelFLOW
User action
   ↓
Update state
   ↓
Derive visible data
   ↓
Render DOM
   ↓
Update status + stats

Ye simple one-way flow larger frontend frameworks ke concepts samajhne ke liye bhi strong foundation hai.

Course concepts used

  • Variables and data types → application state.
  • Conditions → filters and button labels.
  • Functions → reusable logic.
  • Arrays → filter(), map(), reduce().
  • Objects → course records and state.
  • DOM → dynamic card creation.
  • Events → search, filters, reset and progress actions.
  • Modern JS → spread, template literals, arrow callbacks.
  • Async JavaScript → Promise-based initialization.
  • Debugging → console, breakpoints and deliberate test cases.

Challenge extensions

Base project complete hone ke baad in improvements ko khud build karo:

  1. Real API se course data load karo using fetch().
  2. Sort dropdown add karo: title, progress high-to-low, low-to-high.
  3. Course category filter add karo.
  4. Progress change ke liye 25%, 50%, 75%, 100% controls add karo.
  5. URL query parameters me current search/filter reflect karo.
  6. Error simulation button add karke error state test karo.
  7. Automated tests ke liye pure filtering/stat functions separate module me move karo.

Final project checklist

  • Search title/category dono par work karta hai?
  • All / In Progress / Completed filters correct hain?
  • Mark complete state and stats update hote hain?
  • Reset initial state restore karta hai?
  • No-result message visible hai?
  • Keyboard-only use possible hai?
  • Console me unexpected errors nahi hain?
  • Mobile width par cards readable hain?
  • Dynamic text safe textContent se render hota hai?
  • Code functions me clearly organized hai?

Final Task — Rebuild Without Copying

Demo use karne aur chapter samajhne ke baad project ko blank folder se khud rebuild karo.

  1. HTML skeleton banao.
  2. Responsive CSS layout banao.
  3. At least 6 course objects create karo.
  4. State object define karo.
  5. Course cards JS se render karo.
  6. Search implement karo.
  7. Three filters implement karo.
  8. Progress toggle implement karo.
  9. Stats calculate karo.
  10. Reset action add karo.
  11. Status live region add karo.
  12. Keyboard and mobile testing karo.
  13. Breakpoints se one bug debug karo.
  14. Finally code ko small focused functions me review karo.
Course complete

Agar tum is project ko blank folder se independently bana sakte ho aur har major function explain kar sakte ho, to tumne JavaScript fundamentals ko sirf padha nahi — practically apply bhi kiya.