mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-08-10 15:21:22 +02:00
search: pagination and scroll, add range counter, mobile version
This commit is contained in:
@@ -27,34 +27,42 @@ $paginationUrl = static function(int $targetPage) use ($baseParams): string {
|
||||
|
||||
$atFirst = $page <= 1;
|
||||
$atLast = $page >= $totalPages;
|
||||
|
||||
/** Build HTMX attributes for a non-disabled pagination link. */
|
||||
$hxAttrs = static function(string $url): string {
|
||||
return 'hx-get="' . htmlspecialchars($url) . '"'
|
||||
. ' hx-target="#search-results"'
|
||||
. ' hx-swap="outerHTML"'
|
||||
. ' hx-push-url="' . htmlspecialchars($url) . '"';
|
||||
};
|
||||
?>
|
||||
<nav class="pagination-wrap" aria-label="Pagination">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="<?= $paginationUrl(1) ?>"
|
||||
<li class="pagination-edge">
|
||||
<a href="<?= $firstUrl = $paginationUrl(1) ?>"
|
||||
class="pagination-btn<?= $atFirst ? ' disabled' : '' ?>"
|
||||
<?= $atFirst ? 'aria-disabled="true" tabindex="-1"' : '' ?>
|
||||
<?= $atFirst ? 'aria-disabled="true" tabindex="-1"' : $hxAttrs($firstUrl) ?>
|
||||
aria-label="Première page">«</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= $paginationUrl(max(1, $page - 1)) ?>"
|
||||
<a href="<?= $prevUrl = $paginationUrl(max(1, $page - 1)) ?>"
|
||||
class="pagination-btn<?= $atFirst ? ' disabled' : '' ?>"
|
||||
<?= $atFirst ? 'aria-disabled="true" tabindex="-1"' : '' ?>
|
||||
<?= $atFirst ? 'aria-disabled="true" tabindex="-1"' : $hxAttrs($prevUrl) ?>
|
||||
aria-label="Page précédente">‹</a>
|
||||
</li>
|
||||
<li class="pagination-info" aria-current="page">
|
||||
<span class="page-current"><?= $page ?></span> / <?= $totalPages ?>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= $paginationUrl(min($totalPages, $page + 1)) ?>"
|
||||
<a href="<?= $nextUrl = $paginationUrl(min($totalPages, $page + 1)) ?>"
|
||||
class="pagination-btn<?= $atLast ? ' disabled' : '' ?>"
|
||||
<?= $atLast ? 'aria-disabled="true" tabindex="-1"' : '' ?>
|
||||
<?= $atLast ? 'aria-disabled="true" tabindex="-1"' : $hxAttrs($nextUrl) ?>
|
||||
aria-label="Page suivante">›</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?= $paginationUrl($totalPages) ?>"
|
||||
<li class="pagination-edge">
|
||||
<a href="<?= $lastUrl = $paginationUrl($totalPages) ?>"
|
||||
class="pagination-btn<?= $atLast ? ' disabled' : '' ?>"
|
||||
<?= $atLast ? 'aria-disabled="true" tabindex="-1"' : '' ?>
|
||||
<?= $atLast ? 'aria-disabled="true" tabindex="-1"' : $hxAttrs($lastUrl) ?>
|
||||
aria-label="Dernière page">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Search results fragment
|
||||
*
|
||||
* Renders the results header, grid, and pagination.
|
||||
* Used both for the initial page load and for HTMX pagination swaps.
|
||||
*
|
||||
* Required variables:
|
||||
* array $results
|
||||
* int $totalItems
|
||||
* int $totalPages
|
||||
* int $page
|
||||
* array $baseParams
|
||||
* array $coverMap
|
||||
* ?string $validationError
|
||||
*/
|
||||
?>
|
||||
<div id="search-results">
|
||||
<?php if ($validationError): ?>
|
||||
<div class="search-error">⚠ <?= htmlspecialchars($validationError) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$perPage = 15;
|
||||
$from = ($page - 1) * $perPage + 1;
|
||||
$to = min($page * $perPage, $totalItems);
|
||||
?>
|
||||
<output class="search-results-header" role="status">
|
||||
<?php if ($totalItems > 0): ?>
|
||||
<span class="search-results-range"><?= $from ?>–<?= $to ?> sur <?= $totalItems ?> résultat<?= $totalItems > 1 ? 's' : '' ?></span>
|
||||
<span class="search-results-range--compact"><?= $from ?>–<?= $to ?> / <?= $totalItems ?></span>
|
||||
<?php else: ?>
|
||||
<span>0 résultat</span>
|
||||
<?php endif; ?>
|
||||
</output>
|
||||
|
||||
<?php if (!empty($results)): ?>
|
||||
<ul class="results-grid">
|
||||
<?php foreach ($results as $item): ?>
|
||||
<?php $thumb = $coverMap[$item['id']] ?? null; ?>
|
||||
<li><a href="/tfe?id=<?= (int)$item['id'] ?>" class="result-card">
|
||||
<?php if ($thumb): ?>
|
||||
<figure class="result-card__cover">
|
||||
<img src="/media?path=<?= urlencode($thumb) ?>"
|
||||
alt="Couverture — <?= htmlspecialchars($item['title']) ?>"
|
||||
loading="lazy">
|
||||
</figure>
|
||||
<?php else: ?>
|
||||
<div class="result-card__gradient"></div>
|
||||
<?php endif; ?>
|
||||
<span class="result-card__authors"><?= htmlspecialchars($item['authors'] ?? '') ?></span>
|
||||
<span class="result-card__title"><?= htmlspecialchars($item['title']) ?></span>
|
||||
<small class="result-card__meta"><?= htmlspecialchars($item['year']) ?><?php if (!empty($item['orientation'])): ?> · <?= htmlspecialchars($item['orientation']) ?><?php endif; ?></small>
|
||||
</a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<?php include APP_ROOT . '/templates/partials/pagination.php'; ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="search-empty">Aucun résultat pour cette recherche.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -2,105 +2,89 @@
|
||||
<div class="search-error">⚠ <?= htmlspecialchars($validationError) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// Count active filters (excluding 'query' and 'page')
|
||||
$filterKeys = ['year', 'ap_program', 'orientation', 'finality', 'format'];
|
||||
$activeFilterCount = 0;
|
||||
foreach ($filterKeys as $k) {
|
||||
if (!empty($_GET[$k])) $activeFilterCount++;
|
||||
}
|
||||
?>
|
||||
<!-- Filter controls -->
|
||||
<form class="search-controls" method="GET" action="/search">
|
||||
<input type="hidden" name="query" value="<?= htmlspecialchars($_GET['query'] ?? '') ?>">
|
||||
<details class="search-filters-toggle" open>
|
||||
<summary class="search-filters-summary">
|
||||
Filtres
|
||||
<?php if ($activeFilterCount > 0): ?>
|
||||
<span class="search-filters-badge"><?= $activeFilterCount ?></span>
|
||||
<?php endif; ?>
|
||||
</summary>
|
||||
<form class="search-controls" method="GET" action="/search">
|
||||
<input type="hidden" name="query" value="<?= htmlspecialchars($_GET['query'] ?? '') ?>">
|
||||
|
||||
<label class="search-filter-label" for="filter-year">Année
|
||||
<select class="search-filter-select" name="year" id="filter-year">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($years as $y): ?>
|
||||
<option value="<?= (int)$y ?>" <?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'selected' : '' ?>>
|
||||
<?= (int)$y ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="search-filter-label" for="filter-year">Année
|
||||
<select class="search-filter-select" name="year" id="filter-year">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($years as $y): ?>
|
||||
<option value="<?= (int)$y ?>" <?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'selected' : '' ?>>
|
||||
<?= (int)$y ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="search-filter-label" for="filter-ap">AP
|
||||
<select class="search-filter-select" name="ap_program" id="filter-ap">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?= htmlspecialchars($ap['name']) ?>"
|
||||
<?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($ap['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="search-filter-label" for="filter-ap">AP
|
||||
<select class="search-filter-select" name="ap_program" id="filter-ap">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?= htmlspecialchars($ap['name']) ?>"
|
||||
<?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($ap['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="search-filter-label" for="filter-orientation">Orientation
|
||||
<select class="search-filter-select" name="orientation" id="filter-orientation">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= htmlspecialchars($o['name']) ?>"
|
||||
<?= (isset($_GET['orientation']) && $_GET['orientation'] == $o['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="search-filter-label" for="filter-orientation">Orientation
|
||||
<select class="search-filter-select" name="orientation" id="filter-orientation">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= htmlspecialchars($o['name']) ?>"
|
||||
<?= (isset($_GET['orientation']) && $_GET['orientation'] == $o['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="search-filter-label" for="filter-finality">Finalité
|
||||
<select class="search-filter-select" name="finality" id="filter-finality">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($finalityTypes as $f): ?>
|
||||
<option value="<?= htmlspecialchars($f['name']) ?>"
|
||||
<?= (isset($_GET['finality']) && $_GET['finality'] == $f['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($f['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="search-filter-label" for="filter-finality">Finalité
|
||||
<select class="search-filter-select" name="finality" id="filter-finality">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($finalityTypes as $f): ?>
|
||||
<option value="<?= htmlspecialchars($f['name']) ?>"
|
||||
<?= (isset($_GET['finality']) && $_GET['finality'] == $f['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($f['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="search-filter-label" for="filter-format">Format
|
||||
<select class="search-filter-select" name="format" id="filter-format">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($formatTypes as $fmt): ?>
|
||||
<option value="<?= htmlspecialchars($fmt['name']) ?>"
|
||||
<?= (isset($_GET['format']) && $_GET['format'] == $fmt['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($fmt['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="search-filter-label" for="filter-format">Format
|
||||
<select class="search-filter-select" name="format" id="filter-format">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($formatTypes as $fmt): ?>
|
||||
<option value="<?= htmlspecialchars($fmt['name']) ?>"
|
||||
<?= (isset($_GET['format']) && $_GET['format'] == $fmt['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($fmt['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<button type="submit" class="btn btn--primary btn--sm search-apply-btn">Filtrer</button>
|
||||
<a href="/search?query=<?= urlencode($_GET['query'] ?? '') ?>" class="btn btn--secondary btn--sm search-reset-link">Réinitialiser</a>
|
||||
</form>
|
||||
<button type="submit" class="btn btn--primary btn--sm search-apply-btn">Filtrer</button>
|
||||
<a href="/search?query=<?= urlencode($_GET['query'] ?? '') ?>" class="btn btn--secondary btn--sm search-reset-link">Réinitialiser</a>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<main class="search-main" id="main-content">
|
||||
<output class="search-results-header" role="status"><?= $totalItems ?> résultat<?= $totalItems > 1 ? 's' : '' ?></output>
|
||||
|
||||
<?php if (!empty($results)): ?>
|
||||
<ul class="results-grid">
|
||||
<?php foreach ($results as $item): ?>
|
||||
<?php $thumb = $coverMap[$item['id']] ?? null; ?>
|
||||
<li><a href="/tfe?id=<?= (int)$item['id'] ?>" class="result-card">
|
||||
<?php if ($thumb): ?>
|
||||
<figure class="result-card__cover">
|
||||
<img src="/media?path=<?= urlencode($thumb) ?>"
|
||||
alt="Couverture — <?= htmlspecialchars($item['title']) ?>"
|
||||
loading="lazy">
|
||||
</figure>
|
||||
<?php else: ?>
|
||||
<div class="result-card__gradient">
|
||||
<span class="result-card__gradient-author"><?= htmlspecialchars($item['authors'] ?? '') ?></span>
|
||||
<span class="result-card__gradient-title"><?= htmlspecialchars($item['title']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($thumb): ?>
|
||||
<span class="result-card__authors"><?= htmlspecialchars($item['authors'] ?? '') ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="result-card__title"><?= htmlspecialchars($item['title']) ?></span>
|
||||
<small class="result-card__meta"><?= htmlspecialchars($item['year']) ?><?php if (!empty($item['orientation'])): ?> · <?= htmlspecialchars($item['orientation']) ?><?php endif; ?></small>
|
||||
</a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<?php include APP_ROOT . '/templates/partials/pagination.php'; ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="search-empty">Aucun résultat pour cette recherche.</p>
|
||||
<?php endif; ?>
|
||||
<?php include APP_ROOT . '/templates/partials/search-results.php'; ?>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user