Skip to content
This repository was archived by the owner on Jul 20, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/app/controllers/ProductController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ public function viewProducts() {
require_once __DIR__ . "/../views/products.php";
}


public function filterProducts() {
$data = json_decode(file_get_contents("php://input"), true);

$search = $data['search'] ?? '';
$sort = $data['sort'] ?? '';

$products = $this->productModel->getFiltered($search, $sort);

require __DIR__ . "/../views/partials/product_list.php";
}

public function viewSingleProduct() {
if($_SERVER['REQUEST_METHOD'] !== 'GET') {
header('Location: /products');
Expand Down
22 changes: 22 additions & 0 deletions src/app/models/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,28 @@ public function getProductById($id) {
return $stmt->fetch(PDO::FETCH_OBJ);
}

public function getFiltered($search, $sort) {
$query = "SELECT * FROM product p WHERE 1";

if(!empty($search)) {
$query .= " AND name like :search";
}

Comment thread
ken-soares marked this conversation as resolved.
if($sort == "name_desc") {
$query .= " ORDER BY name DESC";
} else if ($sort == "name_asc") {
$query .= " ORDER BY name ASC";
}

$stmt = $this->conn->prepare($query);
if(!empty($search)) {
$stmt->bindValue(":search", "%$search%");
}

$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_OBJ);
}

/**
* Reusable product details used by admin edit forms.
*/
Expand Down
16 changes: 16 additions & 0 deletions src/app/views/partials/product_list.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php foreach ($products as $product) { ?>
<?php $imagePath = !empty($product->image) ? $product->image : '/assets/images/test.jpg'; ?>
<article class="product-item">
<a href="/product?id=<?= htmlspecialchars($product->id); ?>">
<img
src="<?= htmlspecialchars($imagePath); ?>"
alt="<?= htmlspecialchars($product->name); ?>"
>

<div class="product-info">
<h2><?= htmlspecialchars($product->name); ?></h2>
<p><?= htmlspecialchars($product->price); ?>€</p>
</div>
</a>
</article>
<?php } ?>
62 changes: 44 additions & 18 deletions src/app/views/products.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,50 @@
<h1>Product list</h1>
<!--FIXME: fix category table, to organise products by category (those seen on the menu)-->

<div>
<?php foreach ($products as $product) { ?>
<?php $imagePath = !empty($product->image) ? $product->image : '/assets/images/test.jpg'; ?>
<article>
<a href="/product?id=<?php echo htmlspecialchars($product->id); ?>">
<img
src="<?php echo htmlspecialchars($imagePath); ?>"
alt="<?php echo htmlspecialchars($product->name); ?>"
>

<div>
<h2><?php echo htmlspecialchars($product->name); ?></h2>
<p><?php echo htmlspecialchars($product->price); ?>€</p>
</div>
</a>
</article>
<?php } ?>
<div class="sort-filter">
<input type="text" id="search" placeholder="Rechercher...">
<select id="sort">
<option value="">-- Trier --</option>
<option value="name_asc">Nom A-Z</option>
<option value="name_desc">Nom Z-A</option>
</select>
</div>

<div class="products">
<?php include "partials/product_list.php"; ?>
</div>
</main>

<?php include 'partials/footer.php'; ?>
<?php include 'partials/footer.php'; ?>


<script>
function loadProducts() {
const search = document.getElementById('search').value;
const sort = document.getElementById('sort').value;

fetch('/products/filter', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ search, sort })
})
.then(res => res.text())
.then(html => {
document.querySelector('.products').innerHTML = html;
});
}

// events
document.getElementById('search').addEventListener('input', debounce(loadProducts, 300));
document.getElementById('sort').addEventListener('change', loadProducts);

// debounce
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}

</script>
49 changes: 49 additions & 0 deletions src/public/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,52 @@
color: white !important;
transition: all 0.3s ease;
}

h1 {
padding: 10px;
}

.products {
display:flex;
flex-wrap: wrap;
justify-content: space-around;
}

.product-item {
margin: 25px;
width: 250px;
height: 350px;
display: flex;
flex-direction: column;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
transition: transform 0.3s ease-in-out;

}

.product-item:hover {
transform: scale(1.05);
}

.product-item a {
width: 100%;
height: 100%;
display: block;
text-decoration: none;
color: #222;
}

.product-item img {
height: 70%;
width: 100%;
object-fit: cover;
}

.product-info {
width: 100%;
padding: 10px;
}

.sort-filter {
padding-left: 10px;
padding-bottom:25px;
}
5 changes: 5 additions & 0 deletions src/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@
'action' => 'updateAccount',
'access' => ['client']
],
'/products/filter' => [
'controller' => 'ProductController',
'action' => 'filterProducts',
'access' => ['guest', 'client']
],
'/cart' => [
'controller' => 'CartController',
'action' => 'cartAction',
Expand Down