Repository: Jessi-90/Join
Pull request #127: search functionality implemented
Author: login: PeterTr-ust
Created: 2025-04-05T17:41:42Z | Merged: 2025-04-09T18:06:09Z

- Man kann nun nach Tasks suchen
- Der Button mit der Lupe hat noch keine Funktion (konnte auch mit Figma keine Sinnvolle Funktion erkennen)


Diff:
diff --git a/html/board.html b/html/board.html
index e75995e1..25345ebd 100644
--- a/html/board.html
+++ b/html/board.html
@@ -26,7 +26,7 @@
     <script src="../scripts/add_task-script.js" defer></script>
     <script src="../scripts/addTaskOverlayBoard.js" defer></script>
     <script src="../scripts/boardCardDetails.js" defer></script>
-    <script src="../scripts/boardCardDetailsEdit.js" defer></script> 
+    <script src="../scripts/boardCardDetailsEdit.js" defer></script>
     <script src="../scripts/templates/cardDetailsOverlayTemplate.js"></script>
     <script src="../scripts/general-navbarscript.js" defer></script>
     <script src="../scripts/userCredentials.js"></script>
@@ -42,8 +42,8 @@
     <script src="../scripts/pushTaskToDb.js"></script>
     <script src="../scripts/sortContactData.js"></script>
     <script src="../scripts/addTaskDueDate.js"></script>
+    <script src="../scripts/boardSearch.js"></script>
     <script src="../scripts/addTaskSubtasks.js"></script>
-    <script src="../scripts/sortContactData.js"></script>
     <script src="../scripts/authGuard.js"></script>
 </head>
 
@@ -91,7 +91,7 @@
             <h1>Board</h1>
             <div class="search-and-add">
                 <div class="search-input">
-                    <input type="search" placeholder="Find Task" />
+                    <input id="search" type="search" placeholder="Find Task" />
                     <div class="search-input-icons">
                         <img class="vertical-separator" src="../assets/icons/vertical-line-search-input.svg"
                             alt="vertical-line" />
@@ -110,6 +110,7 @@ <h1>Board</h1>
             </div>
         </section>
         <section class="board">
+            <div id="no-results-message" class="no-results d-none">No results found!</div>
             <div class="column" id="to-do-column" ondragleave="removeHighlightCardContainer('to-do-column')"
                 ondragover="dragoverHandler(event); highlightCardContainer('to-do-column')"
                 ondrop="moveCardTo(1, 'to-do-column')">
diff --git a/scripts/addTaskSubtasks.js b/scripts/addTaskSubtasks.js
index 68a6c52a..e8262668 100644
--- a/scripts/addTaskSubtasks.js
+++ b/scripts/addTaskSubtasks.js
@@ -11,7 +11,7 @@ function addSubtask(inputField, listElement) {
         const li = document.createElement("li");
         li.textContent = subtask;
         listElement.appendChild(li);
-        inputField.value = ""; // Clear the input field
+        inputField.value = "";
     }
 }
 
@@ -39,6 +39,10 @@ function setFocusOnInput(event) {
     if (event && event.preventDefault) {
         event.preventDefault();
     }
+    const inputField = document.getElementById("subtasks");
+    if (inputField) {
+        inputField.focus();
+    }
 }
 
 
diff --git a/scripts/add_task-script.js b/scripts/add_task-script.js
index a338bb7c..499f16b0 100644
--- a/scripts/add_task-script.js
+++ b/scripts/add_task-script.js
@@ -261,8 +261,8 @@ function removeSelectedContactsFromStorage() {
  */
 function resetErrorMessages() {
     document.querySelectorAll(".error-message").forEach((errorMessage) => {
-        errorMessage.style.display = "none"; // Hide the error message
-        errorMessage.textContent = ""; // Clear the error message content
+        errorMessage.style.display = "none";
+        errorMessage.textContent = "";
     });
 }
 
diff --git a/scripts/board.js b/scripts/board.js
index 5cd07801..6e4f3d48 100644
--- a/scripts/board.js
+++ b/scripts/board.js
@@ -4,18 +4,36 @@
  */
 let currentTasksData;
 
+/**
+ * @type {object}
+ * A global variable to store the original task data in its full structure.
+ * This variable serves as a reference to reset or restore `currentTasksData`
+ * to its initial state when needed (e.g., when the search input field is cleared).
+ */
+let originalTasksData;
+
 /**
  * The maximum length of the task description before truncating.
  * @type {number}
  */
 let maxLengthTaskDescription = 50;
 
+/**
+ * @type {number}
+ * Sets the minimum number of characters required in the search input
+ * before the search function is executed. Adjust this value to
+ * control when the search logic starts filtering tasks.
+ */
+let minSearchLength = 2;
+
 /**
  * Initializes the application by fetching task data.
  */
 async function init() {
     await fetchTasksData();
     renderTasks(currentTasksData);
+    initializeSearchListener();
+    initializeTaskBackup()
 }
 
 
diff --git a/scripts/boardSearch.js b/scripts/boardSearch.js
new file mode 100644
index 00000000..393b0aac
--- /dev/null
+++ b/scripts/boardSearch.js
@@ -0,0 +1,136 @@
+/**
+ * Adds an event listener to the search input field to monitor user input.
+ * When the input length meets the minimum required length (`minSearchLength`),
+ * the search function (`searchTasks`) is executed. If the input is below the
+ * minimum length or cleared, the original task data is restored using
+ * `checkSearchInput`.
+ */
+function initializeSearchListener() {
+    const searchInput = document.getElementById("search");
+
+    searchInput.addEventListener("input", function () {
+        if (searchInput.value.length >= minSearchLength) {
+            searchTasks();
+        } else {
+            checkSearchInput();
+        }
+    });
+}
+
+
+/**
+ * Creates a deep copy of the current task data (`currentTasksData`) and 
+ * stores it in the global variable `originalTasksData`.
+ * This ensures that the original task data remains unchanged and can 
+ * be restored when needed, such as resetting tasks after a search.
+ */
+function initializeTaskBackup() {
+    originalTasksData = JSON.parse(JSON.stringify(currentTasksData));
+}
+
+
+/**
+ * Handles the task search logic based on user input in the search field. 
+ * Validates the input length, filters tasks using the original task data, 
+ * updates the task list accordingly, and provides user feedback if no 
+ * matching tasks are found.
+ * 
+ * Utilizes helper functions:
+ * - `getSearchInput`: Retrieves and processes the search input.
+ * - `isSearchInputValid`: Validates the length of the search input.
+ * - `filterTasksBySearchInput`: Filters tasks based on the search query.
+ * - `toggleNoResultsMessage`: Toggles the visibility of the "No results found" message.
+ * - `updateTasksAndRender`: Updates the task list and triggers rendering.
+ */
+function searchTasks() {
+    const searchInput = getSearchInput();
+    const noResultsMessage = document.getElementById("no-results-message");
+
+    if (!isSearchInputValid(searchInput)) {
+        updateTasksAndRender(Object.values(originalTasksData));
+        toggleNoResultsMessage(noResultsMessage, Object.values(originalTasksData).length);
+        return;
+    }
+
+    const matchingTasks = filterTasksBySearchInput(searchInput);
+    toggleNoResultsMessage(noResultsMessage, matchingTasks.length);
+    updateTasksAndRender(matchingTasks);
+}
+
+
+/**
+ * Retrieves the user input from the search field and converts it to lowercase.
+ * @returns {string} The processed search input value.
+ */
+function getSearchInput() {
+    return document.getElementById("search").value.toLowerCase();
+}
+
+
+/**
+ * Validates if the search input meets the minimum length requirement.
+ * @param {string} searchInput - The search input value.
+ * @returns {boolean} True if the input length is sufficient, otherwise false.
+ */
+function isSearchInputValid(searchInput) {
+    return searchInput.length >= minSearchLength;
+}
+
+
+/**
+ * Filters tasks based on the search input value, using the original task data as the source.
+ * @param {string} searchInput - The user input from the search field.
+ * @returns {array} An array of tasks that match the search criteria.
+ */
+function filterTasksBySearchInput(searchInput) {
+    return Object.values(originalTasksData).filter(task => {
+        if (typeof task !== "object" || !task.title) {
+            return false;
+        }
+
+        const titleMatch = task.title.toLowerCase().includes(searchInput);
+        const descriptionMatch = task.description
+            ? task.description.toLowerCase().includes(searchInput)
+            : false;
+
+        return titleMatch || descriptionMatch;
+    });
+}
+
+
+/**
+ * Updates `currentTasksData` with the filtered tasks and triggers the rendering.
+ * @param {array} matchingTasks - The filtered array of tasks matching the search input.
+ */
+function updateTasksAndRender(matchingTasks) {
+    currentTasksData = matchingTasks;
+    renderTasks(currentTasksData);
+}
+
+
+/**
+ * Checks whether the search input field is empty. If it is empty,
+ * restores the original task data (`originalTasksData`) to `currentTasksData`,
+ * triggers the rendering of tasks, and ensures the visibility of the
+ * "No results found" message is updated appropriately.
+ */
+function checkSearchInput() {
+    const searchInput = document.getElementById("search").value;
+    const noResultsMessage = document.getElementById("no-results-message");
+
+    if (searchInput.trim() === "") {
+        currentTasksData = JSON.parse(JSON.stringify(originalTasksData));
+        renderTasks(currentTasksData);
+        toggleNoResultsMessage(noResultsMessage, currentTasksData.length); // Sichtbarkeit aktualisieren
+    }
+}
+
+
+/**
+ * Toggles the visibility of the "No results found" message based on the task search results.
+ * @param {HTMLElement} messageElement - The HTML element containing the "No results found" message.
+ * @param {number} matchingTasksLength - The length of the array of matching tasks.
+ */
+function toggleNoResultsMessage(messageElement, matchingTasksLength) {
+    messageElement.classList.toggle('d-none', matchingTasksLength !== 0);
+}
\ No newline at end of file
diff --git a/styles/board.css b/styles/board.css
index 59169fff..4cf80d91 100644
--- a/styles/board.css
+++ b/styles/board.css
@@ -78,6 +78,22 @@ input[type="search"]::-webkit-search-cancel-button {
   z-index: -1;
 }
 
+.board {
+  position: relative;
+}
+
+.no-results {
+  display: block;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  color: #ff0000;
+  font-size: 16px;
+  font-weight: bold;
+  text-align: center;
+}
+
 .add-task-btn {
   display: flex;
   align-items: center;


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 2 out of 4 changed files in this pull request and generated no comments.

<details>
<summary>Files not reviewed (2)</summary>

* **html/board.html**: Language not supported
* **styles/board.css**: Language not supported
</details>



state: COMMENTED
author_association: NONE
submitted_at: 2025-04-06T08:12:35Z
user: login: MrFox993
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-06T08:41:31Z
user: login: PeterTr-ust
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T08:23:22Z
user: login: PeterTr-ust
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T12:17:57Z

Review comments:
diff_hunk: @@ -108,6 +108,7 @@ <h1>Board</h1>
             </div>
         </section>
         <section class="board">
+            <div id="no-results-message" class="no-results" style="display: none;">No results found!</div>
path: html/board.html
user: login: MrFox993
body: wir haben doch die Klasse d-none, wieso codest du den style hard?
created_at: 2025-04-06T08:38:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,140 @@
+/**
+ * Adds an event listener to the search input field to monitor user input.
+ * When the input length meets the minimum required length (`minSearchLength`),
+ * the search function (`searchTasks`) is executed. If the input is below the
+ * minimum length or cleared, the original task data is restored using
+ * `checkSearchInput`.
+ */
+function initializeSearchListener() {
+    const searchInput = document.getElementById("search");
+
+    searchInput.addEventListener("input", function () {
+        if (searchInput.value.length >= minSearchLength) {
+            searchTasks();
+        } else {
+            checkSearchInput();
+        }
+    });
+}
+
+
+/**
+ * Creates a deep copy of the current task data (`currentTasksData`) and 
+ * stores it in the global variable `originalTasksData`.
+ * This ensures that the original task data remains unchanged and can 
+ * be restored when needed, such as resetting tasks after a search.
+ */
+function initializeTaskBackup() {
+    originalTasksData = JSON.parse(JSON.stringify(currentTasksData));
+}
+
+
+/**
+ * Handles the task search logic based on user input in the search field. 
+ * Validates the input length, filters tasks using the original task data, 
+ * updates the task list accordingly, and provides user feedback if no 
+ * matching tasks are found.
+ * 
+ * Utilizes helper functions:
+ * - `getSearchInput`: Retrieves and processes the search input.
+ * - `isSearchInputValid`: Validates the length of the search input.
+ * - `filterTasksBySearchInput`: Filters tasks based on the search query.
+ * - `toggleNoResultsMessage`: Toggles the visibility of the "No results found" message.
+ * - `updateTasksAndRender`: Updates the task list and triggers rendering.
+ */
+function searchTasks() {
+    const searchInput = getSearchInput();
+
+    const noResultsMessage = document.getElementById("no-results-message");
+
+    if (!isSearchInputValid(searchInput)) {
+        updateTasksAndRender(Object.values(originalTasksData));
+        toggleNoResultsMessage(noResultsMessage, Object.values(originalTasksData).length);
+        return;
+    }
+
+    const matchingTasks = filterTasksBySearchInput(searchInput);
+
+    toggleNoResultsMessage(noResultsMessage, matchingTasks.length);
+
+    updateTasksAndRender(matchingTasks);
+}
+
+
+/**
+ * Retrieves the user input from the search field and converts it to lowercase.
+ * @returns {string} The processed search input value.
+ */
+function getSearchInput() {
+    return document.getElementById("search").value.toLowerCase();
+}
+
+
+/**
+ * Validates if the search input meets the minimum length requirement.
+ * @param {string} searchInput - The search input value.
+ * @returns {boolean} True if the input length is sufficient, otherwise false.
+ */
+function isSearchInputValid(searchInput) {
+    return searchInput.length >= minSearchLength;
+}
+
+
+/**
+ * Filters tasks based on the search input value, using the original task data as the source.
+ * @param {string} searchInput - The user input from the search field.
+ * @returns {array} An array of tasks that match the search criteria.
+ */
+function filterTasksBySearchInput(searchInput) {
+    return Object.values(originalTasksData).filter(task => {
+        if (typeof task !== "object" || !task.title) {
+            return false;
+        }
+
+        const titleMatch = task.title.toLowerCase().includes(searchInput);
+        const descriptionMatch = task.description
+            ? task.description.toLowerCase().includes(searchInput)
+            : false;
+
+        return titleMatch || descriptionMatch;
+    });
+}
+
+
+/**
+ * Updates `currentTasksData` with the filtered tasks and triggers the rendering.
+ * @param {array} matchingTasks - The filtered array of tasks matching the search input.
+ */
+function updateTasksAndRender(matchingTasks) {
+    currentTasksData = matchingTasks;
+    renderTasks(currentTasksData);
+}
+
+
+/**
+ * Checks whether the search input field is empty. If it is empty, 
+ * restores the original task data (`originalTasksData`) to `currentTasksData` 
+ * and triggers the rendering of tasks.
+ */
+function checkSearchInput() {
+    const searchInput = document.getElementById("search").value;
+
+    if (searchInput.trim() === "") {
+        currentTasksData = JSON.parse(JSON.stringify(originalTasksData));
+        renderTasks(currentTasksData);
+    }
+}
+
+
+/**
+ * Toggles the visibility of the "No results found" message based on the task search results.
+ * @param {HTMLElement} messageElement - The HTML element containing the "No results found" message.
+ * @param {number} matchingTasksLength - The length of the array of matching tasks.
+ */
+function toggleNoResultsMessage(messageElement, matchingTasksLength) {
path: scripts/boardSearch.js
user: login: MrFox993
body: wenn du die Klasse d-none nimmst, könntest du hier mit toggle arbeiten.
Spart ein paar Zeilen Code 😅
created_at: 2025-04-06T08:40:26Z
author_association: COLLABORATOR
diff_hunk: @@ -78,6 +78,21 @@ input[type="search"]::-webkit-search-cancel-button {
   z-index: -1;
 }
 
+.board {
+  position: relative;
+}
+
+.no-results {
path: styles/board.css
user: login: MrFox993
body: und hier als standard display:block hinterlegen.
created_at: 2025-04-06T08:41:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,140 @@
+/**
+ * Adds an event listener to the search input field to monitor user input.
+ * When the input length meets the minimum required length (`minSearchLength`),
+ * the search function (`searchTasks`) is executed. If the input is below the
+ * minimum length or cleared, the original task data is restored using
+ * `checkSearchInput`.
+ */
+function initializeSearchListener() {
+    const searchInput = document.getElementById("search");
+
+    searchInput.addEventListener("input", function () {
+        if (searchInput.value.length >= minSearchLength) {
+            searchTasks();
+        } else {
+            checkSearchInput();
+        }
+    });
+}
+
+
+/**
+ * Creates a deep copy of the current task data (`currentTasksData`) and 
+ * stores it in the global variable `originalTasksData`.
+ * This ensures that the original task data remains unchanged and can 
+ * be restored when needed, such as resetting tasks after a search.
+ */
+function initializeTaskBackup() {
+    originalTasksData = JSON.parse(JSON.stringify(currentTasksData));
+}
+
+
+/**
+ * Handles the task search logic based on user input in the search field. 
+ * Validates the input length, filters tasks using the original task data, 
+ * updates the task list accordingly, and provides user feedback if no 
+ * matching tasks are found.
+ * 
+ * Utilizes helper functions:
+ * - `getSearchInput`: Retrieves and processes the search input.
+ * - `isSearchInputValid`: Validates the length of the search input.
+ * - `filterTasksBySearchInput`: Filters tasks based on the search query.
+ * - `toggleNoResultsMessage`: Toggles the visibility of the "No results found" message.
+ * - `updateTasksAndRender`: Updates the task list and triggers rendering.
+ */
+function searchTasks() {
+    const searchInput = getSearchInput();
+
+    const noResultsMessage = document.getElementById("no-results-message");
+
+    if (!isSearchInputValid(searchInput)) {
+        updateTasksAndRender(Object.values(originalTasksData));
+        toggleNoResultsMessage(noResultsMessage, Object.values(originalTasksData).length);
+        return;
+    }
+
+    const matchingTasks = filterTasksBySearchInput(searchInput);
+
+    toggleNoResultsMessage(noResultsMessage, matchingTasks.length);
+
+    updateTasksAndRender(matchingTasks);
+}
+
+
+/**
+ * Retrieves the user input from the search field and converts it to lowercase.
+ * @returns {string} The processed search input value.
+ */
+function getSearchInput() {
+    return document.getElementById("search").value.toLowerCase();
+}
+
+
+/**
+ * Validates if the search input meets the minimum length requirement.
+ * @param {string} searchInput - The search input value.
+ * @returns {boolean} True if the input length is sufficient, otherwise false.
+ */
+function isSearchInputValid(searchInput) {
+    return searchInput.length >= minSearchLength;
+}
+
+
+/**
+ * Filters tasks based on the search input value, using the original task data as the source.
+ * @param {string} searchInput - The user input from the search field.
+ * @returns {array} An array of tasks that match the search criteria.
+ */
+function filterTasksBySearchInput(searchInput) {
+    return Object.values(originalTasksData).filter(task => {
+        if (typeof task !== "object" || !task.title) {
+            return false;
+        }
+
+        const titleMatch = task.title.toLowerCase().includes(searchInput);
+        const descriptionMatch = task.description
+            ? task.description.toLowerCase().includes(searchInput)
+            : false;
+
+        return titleMatch || descriptionMatch;
+    });
+}
+
+
+/**
+ * Updates `currentTasksData` with the filtered tasks and triggers the rendering.
+ * @param {array} matchingTasks - The filtered array of tasks matching the search input.
+ */
+function updateTasksAndRender(matchingTasks) {
+    currentTasksData = matchingTasks;
+    renderTasks(currentTasksData);
+}
+
+
+/**
+ * Checks whether the search input field is empty. If it is empty, 
+ * restores the original task data (`originalTasksData`) to `currentTasksData` 
+ * and triggers the rendering of tasks.
+ */
+function checkSearchInput() {
+    const searchInput = document.getElementById("search").value;
+
+    if (searchInput.trim() === "") {
+        currentTasksData = JSON.parse(JSON.stringify(originalTasksData));
+        renderTasks(currentTasksData);
+    }
+}
+
+
+/**
+ * Toggles the visibility of the "No results found" message based on the task search results.
+ * @param {HTMLElement} messageElement - The HTML element containing the "No results found" message.
+ * @param {number} matchingTasksLength - The length of the array of matching tasks.
+ */
+function toggleNoResultsMessage(messageElement, matchingTasksLength) {
path: scripts/boardSearch.js
user: login: PeterTr-ust
body: Da  hast du natürlich recht, ich habs angepasst :)
created_at: 2025-04-07T08:23:22Z
author_association: COLLABORATOR
diff_hunk: @@ -78,6 +78,21 @@ input[type="search"]::-webkit-search-cancel-button {
   z-index: -1;
 }
 
+.board {
+  position: relative;
+}
+
+.no-results {
path: styles/board.css
user: login: PeterTr-ust
body: Hab es nun drinnen.
created_at: 2025-04-07T12:17:56Z
author_association: COLLABORATOR

PR comments:
user: login: PeterTr-ust
created_at: 2025-04-08T13:46:29Z
body: Wollte gerade die Konflikte lösen.

In der addTaskSubtasks.js gibt es folgenden Konflikt:

/**
 * Sets focus to the input field with the ID 'subtasks'.
 * Prevents the default action of the event, which may include form submission.
 *
 * @param {Event} event - The event triggered by the button click.
 */
function setFocusOnInput(event) {
<<<<<<< Feature-boardSearchFunction
    event.preventDefault();
    const inputField = document.getElementById("subtasks");
    inputField.focus();
=======
    if (event && event.preventDefault) {
        event.preventDefault();
    }
>>>>>>> main
}


Derzeit sieht also die Funktion so aus:
/**
 * Sets focus to the input field with the ID 'subtasks'.
 * Prevents the default action of the event, which may include form submission.
 *
 * @param {Event} event - The event triggered by the button click.
 */
function setFocusOnInput(event) {
    if (event && event.preventDefault) {
        event.preventDefault();
    }
}

Das entspricht nicht der Beschreibung und Namen der Funktion.

Wisst ihr dazu etwas?
Benötigen wir eine weitere Funktion?

Wir können uns das auch gerne morgen persönlich ansehen.
author_association: COLLABORATOR
user: login: Jessi-90
created_at: 2025-04-09T08:19:49Z
body: Habe die Konflikte gelöst :-D 
Schau gern nochmal nach.
author_association: OWNER