diff --git a/templates/index.html b/templates/index.html index 7971f05..f125098 100644 --- a/templates/index.html +++ b/templates/index.html @@ -96,6 +96,13 @@ إضافة يدوية + + {# Search input box for fuzzy finding #} +
+ + +
+
@@ -111,7 +118,7 @@ - + {% for student in students %} @@ -131,12 +138,16 @@ {% else %} - + {% endfor %}
المحفوظات
{{ student['id'] }}{{ student['memorizing'] }}
لم تتم إضافة أي طالب بعد.
+ {# Message to display when no search results are found #} +
@@ -152,6 +163,129 @@ const importForm = document.getElementById('import-form'); const submitAddButton = document.getElementById('submit-add-button'); + // New elements for search functionality + const searchInput = document.getElementById('search-input'); + const studentsTableBody = document.getElementById('students-table-body'); + const noStudentsRow = document.getElementById('no-students-row'); + const noSearchResultsDiv = document.getElementById('no-search-results'); + + // Store original student data and row elements for efficient filtering and restoration + const allStudentData = []; + studentsTableBody.querySelectorAll('tr').forEach(row => { + if (row.id === 'no-students-row') { + return; // Skip the "no students" row from data processing + } + allStudentData.push({ + element: row, + student_name: row.children[1] ? row.children[1].textContent : '', + parent_name: row.children[3] ? row.children[3].textContent : '', + // Store original HTML of potentially highlighted cells to restore them later + originalStudentNameHTML: row.children[1] ? row.children[1].innerHTML : '', + originalParentNameHTML: row.children[3] ? row.children[3].innerHTML : '' + }); + }); + + // Function to perform a fuzzy match + // Checks if all characters of the pattern exist sequentially in the text, allowing for other characters in between. + function fuzzyMatch(pattern, text) { + pattern = pattern.toLowerCase(); + text = text.toLowerCase(); + let patternIdx = 0; + let textIdx = 0; + + while (patternIdx < pattern.length && textIdx < text.length) { + if (pattern[patternIdx] === text[textIdx]) { + patternIdx++; + } + textIdx++; + } + return patternIdx === pattern.length; + } + + // Function to highlight occurrences of a term within text + function highlightText(text, term) { + if (!term) return text; + // Escape special characters in the term to be used in regex + const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // Use a regular expression with 'gi' for global and case-insensitive matching + const regex = new RegExp(escapedTerm, 'gi'); + // Replace all occurrences of the term with the highlighted version + return text.replace(regex, match => `${match}`); + } + + // Function to filter students based on search input + function filterStudents() { + const searchTerm = searchInput.value.trim(); + let foundResults = 0; + + // Hide the "no students" row if it exists + if (noStudentsRow) { + noStudentsRow.classList.add('hidden'); + } + + if (searchTerm === '') { + // If search term is empty, show all rows and restore their original content + allStudentData.forEach(data => { + data.element.classList.remove('hidden'); + // Restore original HTML for highlighted cells + if (data.element.children[1]) { + data.element.children[1].innerHTML = data.originalStudentNameHTML; + } + if (data.element.children[3]) { + data.element.children[3].innerHTML = data.originalParentNameHTML; + } + }); + noSearchResultsDiv.classList.add('hidden'); // Hide no results message + // If there were no students initially (allStudentData is empty), show the "no students" row again + if (noStudentsRow && allStudentData.length === 0) { + noStudentsRow.classList.remove('hidden'); + } + return; + } + + // Iterate through the stored student data + allStudentData.forEach(data => { + const studentName = data.student_name; + const parentName = data.parent_name; + + // Perform fuzzy search ONLY on student name or parent name + const isMatch = fuzzyMatch(searchTerm, studentName) || fuzzyMatch(searchTerm, parentName); + + if (isMatch) { + data.element.classList.remove('hidden'); // Show the row + foundResults++; + + // Apply highlighting to the student name cell + if (data.element.children[1]) { + data.element.children[1].innerHTML = highlightText(studentName, searchTerm); + } + // Apply highlighting to the parent name cell + if (data.element.children[3]) { + data.element.children[3].innerHTML = highlightText(parentName, searchTerm); + } + } else { + data.element.classList.add('hidden'); // Hide the row + // Restore original content if the row is hidden + if (data.element.children[1]) { + data.element.children[1].innerHTML = data.originalStudentNameHTML; + } + if (data.element.children[3]) { + data.element.children[3].innerHTML = data.originalParentNameHTML; + } + } + }); + + // Show/hide the "no search results" message based on found results + if (foundResults > 0) { + noSearchResultsDiv.classList.add('hidden'); + } else { + noSearchResultsDiv.classList.remove('hidden'); + } + } + + // Event listener for the search input + searchInput.addEventListener('input', filterStudents); + // When the "Choose File" button is clicked, it programmatically clicks the hidden file input. chooseFileButton.addEventListener('click', () => { csvFileInput.click(); diff --git a/templates/template.html b/templates/template.html index 4b79403..cd055a8 100644 --- a/templates/template.html +++ b/templates/template.html @@ -16,6 +16,13 @@ cursor: not-allowed; opacity: 0.6; } + /* Style for highlighting search results */ + .highlight { + background-color: #fceb00; /* A soft yellow for highlighting */ + font-weight: bold; + padding: 2px 4px; + border-radius: 4px; + }