- Redesigned the main page with a modern look and feel. - Added search and filtering functionality for drills. - Implemented pagination for browsing drills. - Added the ability for users to mark drills as favorites.
256 lines
12 KiB
PHP
256 lines
12 KiB
PHP
<?php
|
|
require_once __DIR__ . '/partials/header.php';
|
|
require_once 'db/config.php';
|
|
require_once 'auth.php';
|
|
|
|
// Pagination settings
|
|
$drillsPerPage = 9;
|
|
$currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
|
if ($currentPage < 1) {
|
|
$currentPage = 1;
|
|
}
|
|
$offset = ($currentPage - 1) * $drillsPerPage;
|
|
|
|
// Fetch drills from the database
|
|
try {
|
|
$pdo = db();
|
|
|
|
// Base query for counting total matching drills
|
|
$userId = $_SESSION['user_id'] ?? 0;
|
|
|
|
// Base query for counting total matching drills
|
|
$countSql = "SELECT COUNT(d.id) FROM drills d WHERE d.is_public = TRUE";
|
|
|
|
// Base query for fetching drills with favorite status
|
|
$sql = "SELECT d.*, CASE WHEN uf.user_id IS NOT NULL THEN 1 ELSE 0 END AS is_favorite
|
|
FROM drills d
|
|
LEFT JOIN user_favorites uf ON d.id = uf.drill_id AND uf.user_id = :userId
|
|
WHERE d.is_public = TRUE";
|
|
|
|
|
|
$params = [];
|
|
|
|
// Search functionality
|
|
$searchTerm = $_GET['search'] ?? '';
|
|
if (!empty($searchTerm)) {
|
|
$filterCondition = " AND (title LIKE ? OR description LIKE ?)";
|
|
$countSql .= $filterCondition;
|
|
$sql .= $filterCondition;
|
|
$params[] = "%" . $searchTerm . "%";
|
|
$params[] = "%" . $searchTerm . "%";
|
|
}
|
|
|
|
// Filter functionality
|
|
$ageGroup = $_GET['age_group'] ?? '';
|
|
if (!empty($ageGroup)) {
|
|
$filterCondition = " AND age_group = ?";
|
|
$countSql .= $filterCondition;
|
|
$sql .= $filterCondition;
|
|
$params[] = $ageGroup;
|
|
}
|
|
|
|
$difficulty = $_GET['difficulty'] ?? '';
|
|
if (!empty($difficulty)) {
|
|
$filterCondition = " AND difficulty = ?";
|
|
$countSql .= $filterCondition;
|
|
$sql .= $filterCondition;
|
|
$params[] = $difficulty;
|
|
}
|
|
|
|
// Get total number of drills that match filters
|
|
$countStmt = $pdo->prepare($countSql);
|
|
$countStmt->execute($params);
|
|
$totalDrills = $countStmt->fetchColumn();
|
|
$totalPages = ceil($totalDrills / $drillsPerPage);
|
|
|
|
// Add ordering and pagination to the main query
|
|
$sql .= " ORDER BY created_at DESC LIMIT :limit OFFSET :offset";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
|
|
$stmt->bindValue(':userId', $userId, PDO::PARAM_INT);
|
|
|
|
// Bind filter parameters
|
|
foreach ($params as $key => $value) {
|
|
$stmt->bindValue($key + 1, $value);
|
|
}
|
|
|
|
// Bind pagination parameters
|
|
$stmt->bindValue(':limit', $drillsPerPage, PDO::PARAM_INT);
|
|
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
|
|
$stmt->execute();
|
|
$drills = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Fetch distinct filter values for dropdowns
|
|
$ageGroups = $pdo->query("SELECT DISTINCT age_group FROM drills WHERE is_public = TRUE ORDER BY age_group")->fetchAll(PDO::FETCH_COLUMN);
|
|
$difficulties = $pdo->query("SELECT DISTINCT difficulty FROM drills WHERE is_public = TRUE ORDER BY difficulty")->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
} catch (PDOException $e) {
|
|
// For production, you would log this error and show a generic error page.
|
|
die("Error fetching drills: " . $e->getMessage());
|
|
}
|
|
?>
|
|
|
|
<header class="py-5 text-center container-fluid">
|
|
<div class="row py-lg-5">
|
|
<div class="col-lg-6 col-md-8 mx-auto">
|
|
<h1 class="display-4 fw-bold">Find Your Perfect Drill</h1>
|
|
<p class="lead text-muted"><?php echo htmlspecialchars($pageDescription); ?></p>
|
|
<p>
|
|
<a href="create_drill.php" class="btn btn-primary my-2">Create a Drill</a>
|
|
<a href="#" class="btn btn-secondary my-2">Browse All</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container">
|
|
<!-- Search and Filter Form -->
|
|
<div class="mb-5 p-4 rounded-3 shadow-sm bg-light">
|
|
<form action="index.php" method="GET" class="row g-3 align-items-end">
|
|
<div class="col-md-5">
|
|
<label for="search" class="form-label">Search</label>
|
|
<input type="text" class="form-control" id="search" name="search" placeholder="e.g., Passing, Dribbling..." value="<?php echo htmlspecialchars($searchTerm); ?>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label for="age_group" class="form-label">Age Group</label>
|
|
<select id="age_group" name="age_group" class="form-select">
|
|
<option value="">All Ages</option>
|
|
<?php foreach ($ageGroups as $ag): ?>
|
|
<option value="<?php echo htmlspecialchars($ag); ?>" <?php echo ($ageGroup === $ag) ? 'selected' : ''; ?>><?php echo htmlspecialchars($ag); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="difficulty" class="form-label">Difficulty</label>
|
|
<select id="difficulty" name="difficulty" class="form-select">
|
|
<option value="">All</option>
|
|
<?php foreach ($difficulties as $d): ?>
|
|
<option value="<?php echo htmlspecialchars($d); ?>" <?php echo ($difficulty === $d) ? 'selected' : ''; ?>><?php echo htmlspecialchars($d); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="submit" class="btn btn-primary w-100">Filter</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<h2 class="mb-4">All Drills</h2>
|
|
|
|
<!-- Drills Grid -->
|
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-4">
|
|
<?php if (empty($drills)): ?>
|
|
<div class="col-12">
|
|
<div class="alert alert-info text-center">No drills found matching your criteria.</div>
|
|
</div>
|
|
<?php else: ?>
|
|
<?php foreach ($drills as $drill): ?>
|
|
<div class="col">
|
|
<div class="card h-100 card-drill shadow-sm">
|
|
<?php if (!empty($drill['image_path'])) : ?>
|
|
<img src="<?php echo htmlspecialchars($drill['image_path']); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($drill['title']); ?>" style="height: 200px; object-fit: cover;">
|
|
<?php elseif (!empty($drill['youtube_url']) && get_youtube_id_from_url($drill['youtube_url'])) : ?>
|
|
<div class="card-img-top ratio ratio-16x9">
|
|
<iframe style="min-height: 200px;" src="https://www.youtube.com/embed/<?php echo get_youtube_id_from_url($drill['youtube_url']); ?>?rel=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
|
</div>
|
|
<?php else : ?>
|
|
<svg class="card-img-top bg-light" width="100%" height="200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Image" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="var(--light-gray)"></rect><text x="50%" y="50%" fill="var(--text-color)" dy=".3em">No Image</text></svg>
|
|
<?php endif; ?>
|
|
<div class="card-body">
|
|
<h5 class="card-title fw-bold"><?php echo htmlspecialchars($drill['title']); ?></h5>
|
|
<p class="card-text text-muted"><?php echo htmlspecialchars(substr($drill['description'], 0, 80)); ?>...</p>
|
|
</div>
|
|
<div class="card-footer bg-transparent border-top-0 d-flex justify-content-between align-items-center">
|
|
<a href="drill.php?id=<?php echo $drill['id']; ?>" class="btn btn-sm btn-outline-primary">View Details</a>
|
|
<div class="d-flex align-items-center">
|
|
<?php if (is_logged_in()): ?>
|
|
<span class="favorite-icon material-icons-outlined me-2 <?php echo $drill['is_favorite'] ? 'is-favorite' : ''; ?>" data-drill-id="<?php echo $drill['id']; ?>">
|
|
<?php echo $drill['is_favorite'] ? 'favorite' : 'favorite_border'; ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
<span class="badge bg-light text-dark me-1"><?php echo htmlspecialchars($drill['age_group']); ?></span>
|
|
<span class="badge
|
|
<?php
|
|
switch (strtolower($drill['difficulty'])) {
|
|
case 'easy': echo 'bg-success'; break;
|
|
case 'medium': echo 'bg-warning text-dark'; break;
|
|
case 'hard': echo 'bg-danger'; break;
|
|
default: echo 'bg-secondary';
|
|
}
|
|
?>">
|
|
<?php echo htmlspecialchars($drill['difficulty']); ?>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<?php if ($totalPages > 1) : ?>
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination justify-content-center">
|
|
<?php
|
|
// Build the query string for pagination links
|
|
$queryParams = $_GET;
|
|
unset($queryParams['page']);
|
|
$queryString = http_build_query($queryParams);
|
|
?>
|
|
|
|
<!-- Previous Page -->
|
|
<li class="page-item <?php echo ($currentPage <= 1) ? 'disabled' : ''; ?>">
|
|
<a class="page-link" href="?page=<?php echo $currentPage - 1; ?>&<?php echo $queryString; ?>" tabindex="-1" aria-disabled="true">Previous</a>
|
|
</li>
|
|
|
|
<!-- Page Numbers -->
|
|
<?php for ($i = 1; $i <= $totalPages; $i++) : ?>
|
|
<li class="page-item <?php echo ($i === $currentPage) ? 'active' : ''; ?>">
|
|
<a class="page-link" href="?page=<?php echo $i; ?>&<?php echo $queryString; ?>"><?php echo $i; ?></a>
|
|
</li>
|
|
<?php endfor; ?>
|
|
|
|
<!-- Next Page -->
|
|
<li class="page-item <?php echo ($currentPage >= $totalPages) ? 'disabled' : ''; ?>">
|
|
<a class="page-link" href="?page=<?php echo $currentPage + 1; ?>&<?php echo $queryString; ?>">Next</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
<?php endif; ?>
|
|
</main>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const favoriteIcons = document.querySelectorAll('.favorite-icon');
|
|
|
|
favoriteIcons.forEach(icon => {
|
|
icon.addEventListener('click', function () {
|
|
const drillId = this.dataset.drillId;
|
|
const isFavorited = this.textContent.trim() === 'favorite';
|
|
|
|
fetch('toggle_favorite.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: `drill_id=${drillId}`
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
this.textContent = isFavorited ? 'favorite_border' : 'favorite';
|
|
} else {
|
|
console.error('Failed to toggle favorite');
|
|
}
|
|
})
|
|
.catch(error => console.error('Error:', error));
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/partials/footer.php'; ?>
|