From bbf8f6ba3667b58cd3f4f12898db600964469c7b Mon Sep 17 00:00:00 2001 From: Anamaria Miranda <anny13miranda@gmail.com> Date: Wed, 26 Mar 2025 01:08:54 +0100 Subject: [PATCH] Added full management insights and wishlits --- server/web_app/public/css/global.css | 23 ++- server/web_app/public/css/insights.css | 55 ++++++++ server/web_app/public/css/users.css | 17 --- server/web_app/public/js/insights.js | 132 ++++++++++++++++++ server/web_app/public/js/users.js | 2 +- .../src/Controller/DashboardController.php | 13 +- server/web_app/templates/dashboard.html.twig | 2 +- server/web_app/templates/insights.html.twig | 68 ++++++++- .../templates/usersManagement.html.twig | 2 +- 9 files changed, 285 insertions(+), 29 deletions(-) diff --git a/server/web_app/public/css/global.css b/server/web_app/public/css/global.css index a868886..00e58ea 100644 --- a/server/web_app/public/css/global.css +++ b/server/web_app/public/css/global.css @@ -37,7 +37,6 @@ * { box-sizing: border-box; font-family: var(--font-family-style); - line-height: var(--line-heigth-normal); font-weight: var(--font-weight-regular); margin: 0; padding: 0; @@ -50,3 +49,25 @@ body { .container { margin: var(--padding-space-large); } + +.table-container { + margin: var(--padding-space-large); + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + background-color: var(--color-complementary); +} + +table th, table td { + border: var(--border-thin) solid var(--color-hover); + padding: var(--padding-space-small); + text-align: left; +} + +table th { + background-color: var(--color-hover); + color: var(--color-complementary); +} diff --git a/server/web_app/public/css/insights.css b/server/web_app/public/css/insights.css index 20d32d7..83179fb 100644 --- a/server/web_app/public/css/insights.css +++ b/server/web_app/public/css/insights.css @@ -1 +1,56 @@ @import url('global.css'); + +.controls { + display: flex; + gap: 20px; + margin-bottom: 20px; +} + +.selector { + display: inline-block; +} + +button { + padding: 8px 16px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +button:disabled { + background-color: grey; + +} + +.active { + background-color: green; + padding: var(--padding-space-small); + border-radius: 5%; +} + +.inactive { + background-color: var(--color-light); + padding: var(--padding-space-small); + border-radius: 5%; +} + +#filterControl { + display: flex; + justify-items: center; + margin: var(--margin-space-large); + flex-wrap: wrap; +} + +#pageTitle { + margin-top: 2%; +} + +select { + padding: 10px; + border: 2px solid #ccc; + border-radius: 5px; + background-color: #f9f9f9; + width: 200px; +} \ No newline at end of file diff --git a/server/web_app/public/css/users.css b/server/web_app/public/css/users.css index ace1e04..8caf7e0 100644 --- a/server/web_app/public/css/users.css +++ b/server/web_app/public/css/users.css @@ -1,22 +1,5 @@ @import url('global.css'); -table { - width: 100%; - border-collapse: collapse; - background-color: var(--color-complementary); -} - -table th, table td { - border: var(--border-thin) solid var(--color-hover); - padding: var(--padding-space-small); - text-align: left; -} - -table th { - background-color: var(--color-hover); - color: var(--color-complementary); -} - button { padding: 6px 12px; margin: var(--margin-space-xsmall); diff --git a/server/web_app/public/js/insights.js b/server/web_app/public/js/insights.js index e69de29..2c3cfab 100644 --- a/server/web_app/public/js/insights.js +++ b/server/web_app/public/js/insights.js @@ -0,0 +1,132 @@ +const ROUTE = 'http://localhost'; +const PORT = '8080'; + +// State management +let currentSort, currentTarget; +let isFetching = false; +const button = document.getElementById("refreshBtn"); +const form = document.getElementById("filterControl"); +const title = document.getElementById("pageTitle"); +const wishTitle = "Top 3 wishlists with the highest total purchase amount"; +const itemTitle = "Top 3 most expensive purchased items"; +const trItem = document.getElementById("itemsTable"); +const trWish = document.getElementById("wishlistsTable"); + +// Initialize from URL +function initFromUrl() { + const path = window.location.pathname; + const segments = path.split("/").filter(segment => segment !== ""); + currentSort = segments[segments.length - 1]; + currentTarget = segments[segments.length - 2]; +} + +// Main data loading function +async function fetchData() { + let url = ''; + if (currentTarget === 'items') { + url = `${ROUTE}:${PORT}/items?sortBy=price&sort=${currentSort}&onlyBought=true`; + } else { + url = `${ROUTE}:${PORT}/wishLists/sortedByTotalBought?sort=${currentSort}`; + } + + try { + const response = await fetch(url); + const data = await response.json(); + + // Populate table based on target + const tableBody = document.getElementById('dataBody'); + tableBody.innerHTML = ''; + + const itemsToDisplay = currentTarget === 'wishlists' ? data.wishLists : data.items; + populateTable(itemsToDisplay); + + title.innerText = currentTarget === "wishlists" ? wishTitle : itemTitle; + + if (currentTarget === "wishlists") { + trItem.hidden = true; + trWish.hidden = false; + } else { + trWish.hidden = true; + trItem.hidden = false; + } + + } catch (error) { + console.error('Error fetching data:', error); + } finally { + isFetching = false; + button.disabled = false; + } +} + +// Table population +function populateTable(data) { + const tableBody = document.getElementById('dataBody'); + + data.forEach(key => { + const row = document.createElement('tr'); + + if (currentTarget === 'items') { + row.innerHTML = ` + <td>${key.id}</td> + <td>${key.title}</td> + <td>${key.description}</td> + <td>${key.price}</td> + <td>${key.wishList.name}</td> + <td class="active">${key.wishList.isActive ? 'Active' : 'Unactive'}</td> + <td>${key.wishList.createdAt}</td> + <td>${key.wishList.expirationDate}</td> + <td>${key.wishList.user.username}</td> + `; + } else { + row.innerHTML = ` + <td>${key.wishList.id}</td> + <td>${key.wishList.name}</td> + <td>${key.wishList.description}</td> + <td>${key.totalPrice}</td> + <td> + <span class="${key.isActive ? 'active' : 'inactive'}"> + ${key.isActive ? 'Active' : 'Inactive'} + </span> + </td> + <td>${key.wishList.user.username}</td> + <td>${key.wishList.createdAt}</td> + <td>${key.wishList.expirationDate}</td> + `; + } + + tableBody.appendChild(row); + }); +} + +// Controlled data loading +function loadData() { + if (isFetching) return; + + isFetching = true; + button.disabled = true; + fetchData(); +} + +// Form submission handler +form.addEventListener("submit", function(event) { + event.preventDefault(); + + currentSort = document.getElementById('sort').value; + currentTarget = document.getElementById('target').value; + + // Update URL without reload + const newUrl = `/dashboard/insights/${currentTarget}/${currentSort}`; + window.history.pushState({ path: newUrl }, '', newUrl); + + loadData(); +}); + +// Handle browser navigation +window.addEventListener('popstate', () => { + initFromUrl(); + loadData(); +}); + +// Initial setup +initFromUrl(); +loadData(); \ No newline at end of file diff --git a/server/web_app/public/js/users.js b/server/web_app/public/js/users.js index c5ee461..6d4fdae 100644 --- a/server/web_app/public/js/users.js +++ b/server/web_app/public/js/users.js @@ -5,7 +5,7 @@ let currentUserId = null; let actionType = null; // New variable to track whether it's block or unblock async function fetchUsers() { - const response = await fetch(`${ROUTE}:${PORT}/users`); + const response = await fetch(`${ROUTE}:${PORT}/users?userRole=user`); const data = await response.json(); const tableBody = document.querySelector('#usersTable tbody'); tableBody.innerHTML = ''; // Clear current table rows diff --git a/server/web_app/src/Controller/DashboardController.php b/server/web_app/src/Controller/DashboardController.php index 0c9a0ec..a353f1e 100644 --- a/server/web_app/src/Controller/DashboardController.php +++ b/server/web_app/src/Controller/DashboardController.php @@ -29,13 +29,12 @@ class DashboardController extends AbstractController return $this->render('usersManagement.html.twig'); } - #[Route('/insights', name: 'manageInsights')] - public function items(): Response + #[Route('/insights/{type}/{order}', name: 'manageInsights', defaults: ['type' => 'wishlists', 'order' => 'asc'])] + public function insights(string $type, string $order): Response { - if (!$this->isGranted('ROLE_ADMIN')) { - return $this->redirectToRoute('error'); - } - - return $this->render('insights.html.twig'); + return $this->render('insights.html.twig', [ + 'target' => $type, + 'sort' => $order, + ]); } } diff --git a/server/web_app/templates/dashboard.html.twig b/server/web_app/templates/dashboard.html.twig index 75ab75c..ccec0af 100644 --- a/server/web_app/templates/dashboard.html.twig +++ b/server/web_app/templates/dashboard.html.twig @@ -6,7 +6,7 @@ {% block body %} <section class="container"> - <a class="card" href="{{ path('manageInsights') }}"> + <a class="card" href="{{ path('manageInsights', {'type': 'items', 'order': 'desc'}, false ) }}"> <article> <img src="{{ asset('img/gifts.png') }}" alt=""> <h4>Insights</h4> diff --git a/server/web_app/templates/insights.html.twig b/server/web_app/templates/insights.html.twig index f682d0e..b783655 100644 --- a/server/web_app/templates/insights.html.twig +++ b/server/web_app/templates/insights.html.twig @@ -1,9 +1,75 @@ {% extends 'base.html.twig' %} {% block styles %} - <link rel="stylesheet" href="{{ asset('css/insights.css') }}"> + <link rel="stylesheet" href="{{ asset('css/insights.css') }}"> {% endblock %} {% block body %} + {% if target == 'items' %} + <h3 id="pageTitle">Top 3 most expensive purchased items</h3> + {% else %} + <h3 id="pageTitle">Top 3 wishlists with the highest total purchase amount</h3> + {% endif %} + + <!-- Controls --> + <form id="filterControl" class="controls"> + <div class="selector"> + <label for="sort">Sort By:</label> + <select id="sort"> + <option value="asc" {% if sort == 'asc' %}selected{% endif %}>Ascending</option> + <option value="desc" {% if sort == 'desc' %}selected{% endif %}>Descending</option> + </select> + </div> + <div class="selector"> + <label for="target">Select Target:</label> + <select id="target"> + <option value="wishlists" {% if target == 'wishlists' %}selected{% endif %}>Wishlists</option> + <option value="items" {% if target == 'items' %}selected{% endif %}>Items</option> + </select> + </div> + <button id="refreshBtn" disabled type="submit">Update</button> + </form> + <!-- Table to show data --> + <section class="table-container"> + <table id="dataTable"> + <thead> + <tr id="itemsTable" + {% if target == 'wishlists' %} + hidden + {% endif %} + > + <th>ID</th> + <th>Title</th> + <th>Description</th> + <th>Price</th> + <th>WishList name</th> + <th>Status</th> + <th>Create at</th> + <th>Expiration date</th> + <th>WishList owner</th> + </tr> + + <tr id="wishlistsTable" + {% if target == 'items' %} + hidden + {% endif %} + > + <th>ID</th> + <th>Name</th> + <th>Description</th> + <th>Total Amount</th> + <th>Status</th> + <th>WishList owner</th> + <th>Creation Date</th> + <th>Expiration date</th> + </tr> + </thead> + <tbody id="dataBody"> + <!-- Data will be populated here dynamically --> + </tbody> + </table> + </section> + + <script src="{{ asset('js/insights.js') }}"></script> {% endblock %} diff --git a/server/web_app/templates/usersManagement.html.twig b/server/web_app/templates/usersManagement.html.twig index 1c16889..93a4a7f 100644 --- a/server/web_app/templates/usersManagement.html.twig +++ b/server/web_app/templates/usersManagement.html.twig @@ -7,7 +7,7 @@ {% block body %} <h3>User control panel</h3> - <article class="container"> + <article class="table-container"> <table id="usersTable"> <thead> <tr> -- GitLab