- version : 0.6
- description : adding fuzzy search to the table of students .
هذا الالتزام موجود في:
@@ -96,6 +96,13 @@
|
|||||||
إضافة يدوية
|
إضافة يدوية
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Search input box for fuzzy finding #}
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="search-input" class="sr-only">البحث عن طالب</label>
|
||||||
|
<input type="text" id="search-input" placeholder="ابحث عن طالب بالاسم أو ولي الأمر..." class="w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 text-right" dir="rtl">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200 text-right">
|
<table class="min-w-full divide-y divide-gray-200 text-right">
|
||||||
<thead class="bg-gray-50">
|
<thead class="bg-gray-50">
|
||||||
@@ -111,7 +118,7 @@
|
|||||||
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">المحفوظات</th>
|
<th class="px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">المحفوظات</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
<tbody class="bg-white divide-y divide-gray-200" id="students-table-body">
|
||||||
{% for student in students %}
|
{% for student in students %}
|
||||||
<tr class="hover:bg-gray-50 transition-colors duration-200">
|
<tr class="hover:bg-gray-50 transition-colors duration-200">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold text-gray-700">{{ student['id'] }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold text-gray-700">{{ student['id'] }}</td>
|
||||||
@@ -131,12 +138,16 @@
|
|||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ student['memorizing'] }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ student['memorizing'] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr id="no-students-row">
|
||||||
<td colspan="10" class="text-center py-6 text-gray-500">لم تتم إضافة أي طالب بعد.</td>
|
<td colspan="10" class="text-center py-6 text-gray-500">لم تتم إضافة أي طالب بعد.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{# Message to display when no search results are found #}
|
||||||
|
<div id="no-search-results" class="text-center py-6 text-gray-500 hidden">
|
||||||
|
لا توجد نتائج بحث مطابقة.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -152,6 +163,129 @@
|
|||||||
const importForm = document.getElementById('import-form');
|
const importForm = document.getElementById('import-form');
|
||||||
const submitAddButton = document.getElementById('submit-add-button');
|
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 => `<span class="highlight">${match}</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
// When the "Choose File" button is clicked, it programmatically clicks the hidden file input.
|
||||||
chooseFileButton.addEventListener('click', () => {
|
chooseFileButton.addEventListener('click', () => {
|
||||||
csvFileInput.click();
|
csvFileInput.click();
|
||||||
|
|||||||
@@ -16,6 +16,13 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.6;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 text-gray-800">
|
<body class="bg-gray-50 text-gray-800">
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم