|
| 1 | +// Global variables |
| 2 | +let allProjects = []; |
| 3 | +let filteredProjects = []; |
| 4 | + |
| 5 | +// DOM elements |
| 6 | +const projectsTableBody = document.getElementById('projectsTableBody'); |
| 7 | +const sizeFilter = document.getElementById('sizeFilter'); |
| 8 | +const difficultyFilter = document.getElementById('difficultyFilter'); |
| 9 | +const searchInput = document.getElementById('searchInput'); |
| 10 | +const resetBtn = document.getElementById('resetBtn'); |
| 11 | +const projectModal = document.getElementById('projectModal'); |
| 12 | +const modalBody = document.getElementById('modalBody'); |
| 13 | +const closeBtn = document.querySelector('.close-btn'); |
| 14 | + |
| 15 | +// Load projects from JSON file |
| 16 | +async function loadProjects() { |
| 17 | + try { |
| 18 | + const categories = ['heuristic', 'nlp', 'ruxailab', 'eye-tracker', 'accessibility', 'user-test', 'sentimental']; |
| 19 | + const projectPromises = categories.map(category => |
| 20 | + fetch(`gsoc-ideas/${category}/2026.json`) |
| 21 | + .then(res => res.ok ? res.json() : null) |
| 22 | + .catch(() => null) |
| 23 | + ); |
| 24 | + |
| 25 | + const results = await Promise.all(projectPromises); |
| 26 | + const allCategoryProjects = results |
| 27 | + .filter(data => data !== null) |
| 28 | + .flatMap(data => data.projects.map(p => ({...p, category: data.category, year: 2026}))); |
| 29 | + |
| 30 | + allProjects = allCategoryProjects; |
| 31 | + |
| 32 | + // Sort by title |
| 33 | + allProjects.sort((a, b) => a.title.localeCompare(b.title)); |
| 34 | + |
| 35 | + filteredProjects = [...allProjects]; |
| 36 | + renderProjects(); |
| 37 | + } catch (error) { |
| 38 | + console.error('Error loading projects:', error); |
| 39 | + console.error('Please refresh the page.'); |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +// Render projects in the table |
| 44 | +function renderProjects() { |
| 45 | + projectsTableBody.innerHTML = ''; |
| 46 | + |
| 47 | + if (filteredProjects.length === 0) { |
| 48 | + projectsTableBody.innerHTML = ` |
| 49 | + <tr> |
| 50 | + <td colspan="8" style="text-align: center; padding: 40px; color: #999;"> |
| 51 | + No projects found matching your filters. Try adjusting your search criteria. |
| 52 | + </td> |
| 53 | + </tr> |
| 54 | + `; |
| 55 | + return; |
| 56 | + } |
| 57 | + |
| 58 | + filteredProjects.forEach(project => { |
| 59 | + const row = document.createElement('tr'); |
| 60 | + |
| 61 | + // Truncate description for table view |
| 62 | + const maxLength = 120; |
| 63 | + const shortDescription = project.description.length > maxLength |
| 64 | + ? project.description.substring(0, maxLength) + '...' |
| 65 | + : project.description; |
| 66 | + |
| 67 | + row.innerHTML = ` |
| 68 | + <td><strong>${project.title}</strong></td> |
| 69 | + <td><span class="size-badge size-${project.size}">${capitalizeFirst(project.size)}</span></td> |
| 70 | + <td>${project.hours}h</td> |
| 71 | + <td><span class="difficulty-badge difficulty-${project.difficulty}">${project.difficulty}</span></td> |
| 72 | + <td>${project.mentor}</td> |
| 73 | + <td> |
| 74 | + <div class="keywords-list"> |
| 75 | + ${project.keywords.map(keyword => `<span class="keyword-tag">${keyword}</span>`).join('')} |
| 76 | + </div> |
| 77 | + </td> |
| 78 | + <td> |
| 79 | + <div class="skills-list"> |
| 80 | + ${project.skills.map(skill => `<span class="skill-tag">${skill}</span>`).join('')} |
| 81 | + </div> |
| 82 | + </td> |
| 83 | + <td class="description-cell">${shortDescription}</td> |
| 84 | + `; |
| 85 | + |
| 86 | + row.addEventListener('click', () => showProjectDetail(project)); |
| 87 | + projectsTableBody.appendChild(row); |
| 88 | + }); |
| 89 | +} |
| 90 | + |
| 91 | +// Filter projects based on all criteria |
| 92 | +function filterProjects() { |
| 93 | + const sizeValue = sizeFilter.value; |
| 94 | + const difficultyValue = difficultyFilter.value; |
| 95 | + const searchValue = searchInput.value.toLowerCase(); |
| 96 | + |
| 97 | + filteredProjects = allProjects.filter(project => { |
| 98 | + // Size filter |
| 99 | + if (sizeValue !== 'all' && project.size !== sizeValue) { |
| 100 | + return false; |
| 101 | + } |
| 102 | + |
| 103 | + // Difficulty filter |
| 104 | + if (difficultyValue !== 'all' && project.difficulty !== difficultyValue) { |
| 105 | + return false; |
| 106 | + } |
| 107 | + |
| 108 | + // Search filter |
| 109 | + if (searchValue) { |
| 110 | + const searchableText = [ |
| 111 | + project.title, |
| 112 | + project.description, |
| 113 | + project.mentor, |
| 114 | + ...project.keywords, |
| 115 | + ...project.skills |
| 116 | + ].join(' ').toLowerCase(); |
| 117 | + |
| 118 | + if (!searchableText.includes(searchValue)) { |
| 119 | + return false; |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + return true; |
| 124 | + }); |
| 125 | + |
| 126 | + renderProjects(); |
| 127 | +} |
| 128 | + |
| 129 | +// Reset all filters |
| 130 | +function resetFilters() { |
| 131 | + sizeFilter.value = 'all'; |
| 132 | + difficultyFilter.value = 'all'; |
| 133 | + searchInput.value = ''; |
| 134 | + filterProjects(); |
| 135 | +} |
| 136 | + |
| 137 | +// Show project detail in modal |
| 138 | +function showProjectDetail(project) { |
| 139 | + let modalContent = ` |
| 140 | + <div class="modal-header"> |
| 141 | + <h2 class="modal-title">${project.title}</h2> |
| 142 | + <div class="modal-meta"> |
| 143 | + <span class="year-badge">${project.year}</span> |
| 144 | + <span class="size-badge size-${project.size}">${capitalizeFirst(project.size)} Project</span> |
| 145 | + <span class="difficulty-badge difficulty-${project.difficulty}">${project.difficulty}</span> |
| 146 | + </div> |
| 147 | + </div> |
| 148 | + |
| 149 | + <div class="modal-section"> |
| 150 | + <h3>Description</h3> |
| 151 | + <p>${project.description}</p> |
| 152 | + </div> |
| 153 | + `; |
| 154 | + |
| 155 | + // Add Key Features if available |
| 156 | + if (project.keyFeatures && project.keyFeatures.length > 0) { |
| 157 | + modalContent += ` |
| 158 | + <div class="modal-section"> |
| 159 | + <h3>Key Features</h3> |
| 160 | + <ul class="modal-features-list"> |
| 161 | + ${project.keyFeatures.map(feature => `<li>${feature}</li>`).join('')} |
| 162 | + </ul> |
| 163 | + </div> |
| 164 | + `; |
| 165 | + } |
| 166 | + |
| 167 | + // Add Expected Outcome if available |
| 168 | + if (project.expectedOutcome) { |
| 169 | + modalContent += ` |
| 170 | + <div class="modal-section"> |
| 171 | + <h3>Expected Outcome</h3> |
| 172 | + <p>${project.expectedOutcome}</p> |
| 173 | + </div> |
| 174 | + `; |
| 175 | + } |
| 176 | + |
| 177 | + modalContent += ` |
| 178 | + <div class="modal-section"> |
| 179 | + <h3>Project Information</h3> |
| 180 | + <div class="modal-info-grid"> |
| 181 | + <div class="modal-info-item"> |
| 182 | + <div class="modal-info-label">Duration</div> |
| 183 | + <div class="modal-info-value">${project.hours} hours</div> |
| 184 | + </div> |
| 185 | + <div class="modal-info-item"> |
| 186 | + <div class="modal-info-label">Project Size</div> |
| 187 | + <div class="modal-info-value">${capitalizeFirst(project.size)}</div> |
| 188 | + </div> |
| 189 | + <div class="modal-info-item"> |
| 190 | + <div class="modal-info-label">Difficulty</div> |
| 191 | + <div class="modal-info-value">${project.difficulty}</div> |
| 192 | + </div> |
| 193 | + <div class="modal-info-item"> |
| 194 | + <div class="modal-info-label">Mentor</div> |
| 195 | + <div class="modal-info-value">${project.mentor}</div> |
| 196 | + </div> |
| 197 | + </div> |
| 198 | + </div> |
| 199 | + |
| 200 | + <div class="modal-section"> |
| 201 | + <h3>Keywords</h3> |
| 202 | + <div class="modal-list"> |
| 203 | + ${project.keywords.map(keyword => `<span class="modal-list-item">${keyword}</span>`).join('')} |
| 204 | + </div> |
| 205 | + </div> |
| 206 | + |
| 207 | + <div class="modal-section"> |
| 208 | + <h3>Required Skills</h3> |
| 209 | + <div class="modal-list"> |
| 210 | + ${project.skills.map(skill => `<span class="modal-list-item">${skill}</span>`).join('')} |
| 211 | + </div> |
| 212 | + </div> |
| 213 | + `; |
| 214 | + |
| 215 | + modalBody.innerHTML = modalContent; |
| 216 | + projectModal.classList.add('show'); |
| 217 | + document.body.style.overflow = 'hidden'; |
| 218 | +} |
| 219 | + |
| 220 | +// Hide modal |
| 221 | +function hideModal() { |
| 222 | + projectModal.classList.remove('show'); |
| 223 | + document.body.style.overflow = 'auto'; |
| 224 | +} |
| 225 | + |
| 226 | +// Helper function to capitalize first letter |
| 227 | +function capitalizeFirst(str) { |
| 228 | + return str.charAt(0).toUpperCase() + str.slice(1); |
| 229 | +} |
| 230 | + |
| 231 | +// Event listeners |
| 232 | +sizeFilter.addEventListener('change', filterProjects); |
| 233 | +difficultyFilter.addEventListener('change', filterProjects); |
| 234 | +searchInput.addEventListener('input', filterProjects); |
| 235 | +resetBtn.addEventListener('click', resetFilters); |
| 236 | + |
| 237 | +// Modal event listeners |
| 238 | +closeBtn.addEventListener('click', hideModal); |
| 239 | +projectModal.addEventListener('click', (e) => { |
| 240 | + if (e.target === projectModal) { |
| 241 | + hideModal(); |
| 242 | + } |
| 243 | +}); |
| 244 | + |
| 245 | +// Close modal on Escape key |
| 246 | +document.addEventListener('keydown', (e) => { |
| 247 | + if (e.key === 'Escape' && projectModal.classList.contains('show')) { |
| 248 | + hideModal(); |
| 249 | + } |
| 250 | +}); |
| 251 | + |
| 252 | +// Initialize on page load |
| 253 | +document.addEventListener('DOMContentLoaded', () => { |
| 254 | + loadProjects(); |
| 255 | +}); |
| 256 | + |
| 257 | +// Add some statistics display |
| 258 | +function getProjectStatistics() { |
| 259 | + const stats = { |
| 260 | + byYear: {}, |
| 261 | + bySize: {}, |
| 262 | + byDifficulty: {} |
| 263 | + }; |
| 264 | + |
| 265 | + allProjects.forEach(project => { |
| 266 | + // Count by year |
| 267 | + stats.byYear[project.year] = (stats.byYear[project.year] || 0) + 1; |
| 268 | + |
| 269 | + // Count by size |
| 270 | + stats.bySize[project.size] = (stats.bySize[project.size] || 0) + 1; |
| 271 | + |
| 272 | + // Count by difficulty |
| 273 | + stats.byDifficulty[project.difficulty] = (stats.byDifficulty[project.difficulty] || 0) + 1; |
| 274 | + }); |
| 275 | + |
| 276 | + return stats; |
| 277 | +} |
| 278 | + |
| 279 | +// Log statistics to console for debugging |
| 280 | +window.addEventListener('load', () => { |
| 281 | + setTimeout(() => { |
| 282 | + if (allProjects.length > 0) { |
| 283 | + console.log('Project Statistics:', getProjectStatistics()); |
| 284 | + console.log('Total Projects:', allProjects.length); |
| 285 | + } |
| 286 | + }, 1000); |
| 287 | +}); |
0 commit comments