From 234d7bae40545e41d8956cade90564158464b98d Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Fri, 3 Apr 2026 12:29:09 +0200 Subject: [PATCH] admin/index.php: add server-side pagination (25/page) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Database::getThesesListCount(array $filters) — runs the same WHERE clauses as getThesesList() but with COUNT(DISTINCT t.id); used to compute total pages without loading all rows. - Extend Database::getThesesList() with $limit/$offset parameters; when $limit > 0 appends LIMIT/OFFSET and re-binds positional params individually to avoid the PDO mixed-style restriction. - Fix getThesesList() SELECT: add LEFT JOIN access_types + at.name as access_type — the column was referenced in the template but never fetched. - Wire admin/index.php: read ?page=, compute $totalPages/$offset, pass $perPage=25 + $offset to getThesesList(); include pagination.php partial below the table with filter-preserving $baseParams. - Add result-count line (

) showing "X–Y sur Z TFE" when multiple pages exist. - Add .admin-body .pagination-wrap / .pagination-btn / .pagination-info styles to admin.css (scoped to .admin-body to avoid colliding with public pages). --- TODO.md | 2 + public/admin/index.php | 37 ++++++++++++--- public/assets/css/admin.css | 72 +++++++++++++++++++++++++++++ src/Database.php | 77 +++++++++++++++++++++++++++----- todo/01-css-semantic-refactor.md | 18 ++++---- todo/02-php-components.md | 2 +- 6 files changed, 183 insertions(+), 25 deletions(-) diff --git a/TODO.md b/TODO.md index 0dd5f8d..891173e 100644 --- a/TODO.md +++ b/TODO.md @@ -11,6 +11,8 @@ Pending tasks have been split into topic files under [`todo/`](todo/README.md): ## Recently completed (this session) +- [x] `admin/index.php` — server-side pagination (25/page); `Database::getThesesListCount()` added; `getThesesList()` extended with `$limit`/`$offset`; `access_type` JOIN added to query (was missing); result-count meta line added; `.pagination-wrap` + `.pagination-btn` + `.pagination-info` styles added to `admin.css` + - [x] `checkbox-list.php` — replaced `

` with `
    ` (WCAG 1.3.1 fix) - [x] `admin.css` — replaced `.admin-checkbox-list` with `.admin-body fieldset.admin-checkbox-group > ul` semantic selectors; added `span.admin-row-label` as visible label column counterpart - [x] `login.php` — wrapped content in `
    ` landmark diff --git a/public/admin/index.php b/public/admin/index.php index e42b85b..6863ced 100644 --- a/public/admin/index.php +++ b/public/admin/index.php @@ -21,7 +21,14 @@ try { if ($yearFilter) $filters['year'] = $yearFilter; if ($orientationFilter) $filters['orientation'] = $orientationFilter; - $theses = $db->getThesesList($filters); + $perPage = 25; + $page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1; + $totalCount = $db->getThesesListCount($filters); + $totalPages = $totalCount > 0 ? (int) ceil($totalCount / $perPage) : 1; + $page = min($page, $totalPages); + $offset = ($page - 1) * $perPage; + + $theses = $db->getThesesList($filters, $perPage, $offset); $stats = $db->getThesesStats(); $years = $db->getAllYears(); $orientations = $db->getAllOrientations(); @@ -74,14 +81,14 @@ document.addEventListener('DOMContentLoaded', () => {
    diff --git a/public/assets/css/admin.css b/public/assets/css/admin.css index d9b3eed..74d074d 100644 --- a/public/assets/css/admin.css +++ b/public/assets/css/admin.css @@ -343,6 +343,29 @@ color: var(--text-primary); } +.admin-maintenance-bar form { + display: inline; +} + +/* Result-count line above the thesis table */ +.admin-list-meta { + color: var(--text-secondary); + font-size: 0.85rem; + margin-bottom: 0.5rem; +} + +/* Empty-state message below the thesis table */ +.admin-empty { + color: var(--text-secondary); + padding: 1rem 0; +} + +/* Identifier column in the thesis table */ +.admin-table-id { + color: var(--text-secondary); + font-size: 0.8rem; +} + /* ── Filters bar ────────────────────────────────────────────────────────── */ .admin-filters { display: flex; @@ -808,3 +831,52 @@ color: var(--text-secondary); text-decoration: underline; } + +/* ── Pagination (admin list) ─────────────────────────────────────────────── */ +.admin-body .pagination-wrap { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + padding: 1.5rem 0 0.5rem; +} + +.admin-body .pagination-wrap ul { + display: flex; + gap: 0.25rem; + list-style: none; + margin: 0; + padding: 0; +} + +.admin-body .pagination-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2.75rem; + min-height: 2.75rem; + padding: 0 0.6rem; + border: 1px solid var(--border-secondary); + border-radius: 3px; + color: var(--text-primary); + font-size: 0.9rem; + text-decoration: none; + transition: border-color 0.15s, color 0.15s; +} + +.admin-body .pagination-btn:hover:not(.disabled) { + border-color: var(--admin-purple); + color: var(--admin-purple); +} + +.admin-body .pagination-btn.disabled { + opacity: 0.3; + cursor: not-allowed; + pointer-events: none; +} + +.admin-body .pagination-info { + font-size: 0.9rem; + color: var(--text-secondary); + padding: 0 0.5rem; +} diff --git a/src/Database.php b/src/Database.php index e0e0c11..11b5cbf 100644 --- a/src/Database.php +++ b/src/Database.php @@ -524,14 +524,12 @@ class Database { * @param array $filters * @return array */ - public function getThesesList(array $filters = []): array { - $sql = "SELECT - t.id, t.identifier, t.title, t.subtitle, t.year, - o.name as orientation, - ap.name as ap_program, - GROUP_CONCAT(DISTINCT a.name) as authors, - t.submitted_at, - t.is_published + /** + * Count theses matching the given admin filters (no LIMIT). + * Used alongside getThesesList() to calculate total pages. + */ + public function getThesesListCount(array $filters = []): int { + $sql = "SELECT COUNT(DISTINCT t.id) FROM theses t LEFT JOIN orientations o ON t.orientation_id = o.id LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id @@ -559,10 +557,69 @@ class Database { $params[] = intval($filters['orientation']); } - $sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC"; - $stmt = $this->pdo->prepare($sql); $stmt->execute($params); + return (int) $stmt->fetchColumn(); + } + + public function getThesesList(array $filters = [], int $limit = 0, int $offset = 0): array { + $sql = "SELECT + t.id, t.identifier, t.title, t.subtitle, t.year, + o.name as orientation, + ap.name as ap_program, + GROUP_CONCAT(DISTINCT a.name) as authors, + t.submitted_at, + t.is_published, + at.name as access_type + FROM theses t + LEFT JOIN orientations o ON t.orientation_id = o.id + LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id + LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id + LEFT JOIN authors a ON ta.author_id = a.id + LEFT JOIN access_types at ON t.access_type_id = at.id + WHERE 1=1"; + + $params = []; + + if (!empty($filters['search'])) { + $sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)"; + $searchParam = '%' . $filters['search'] . '%'; + $params[] = $searchParam; + $params[] = $searchParam; + $params[] = $searchParam; + } + + if (!empty($filters['year'])) { + $sql .= " AND t.year = ?"; + $params[] = intval($filters['year']); + } + + if (!empty($filters['orientation'])) { + $sql .= " AND t.orientation_id = ?"; + $params[] = intval($filters['orientation']); + } + + $sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC"; + + if ($limit > 0) { + $sql .= " LIMIT :limit OFFSET :offset"; + } + + $stmt = $this->pdo->prepare($sql); + + // Bind named params only when LIMIT is used (mix of ? and : is not allowed). + if ($limit > 0) { + // Re-bind all positional params by index. + foreach ($params as $i => $val) { + $stmt->bindValue($i + 1, $val); + } + $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); + $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + } else { + $stmt->execute($params); + } + return $stmt->fetchAll(); } diff --git a/todo/01-css-semantic-refactor.md b/todo/01-css-semantic-refactor.md index d3c5a17..7b8a9d4 100644 --- a/todo/01-css-semantic-refactor.md +++ b/todo/01-css-semantic-refactor.md @@ -4,15 +4,15 @@ - [ ] **`admin.css`**: Replace `.admin-main` with `.admin-body main` - [ ] **`admin.css`**: Replace `.admin-page-title` with `.admin-body main > h1` -- [ ] **`admin.css`**: Replace `.admin-alert` / `.admin-alert--error` / `.admin-alert--success` with `[role="alert"]` / `data-type="error|success"` attribute +- [x] **`admin.css`**: Replace `.admin-alert` / `.admin-alert--error` / `.admin-alert--success` with `[role="alert"]` / `data-type="error|success"` attribute - [ ] **`admin.css`**: Replace `.admin-form-row` with `.admin-body form > div` or CSS grid on `` children - [ ] **`admin.css`**: Replace `.admin-label` with `.admin-body form label` - [ ] **`admin.css`**: Replace `.admin-input` / `.admin-select` / `.admin-textarea` with native element selectors -- [ ] **`admin.css`**: Replace `.admin-hint` with `.admin-body form small` +- [x] **`admin.css`**: Replace `.admin-hint` with `.admin-body form small` - [ ] **`admin.css`**: Replace `.admin-table` with `.admin-body table` - [ ] **`admin.css`**: Replace `.admin-fieldset` / `.admin-fieldset-legend` with `.admin-body fieldset` / `.admin-body legend` -- [ ] **`main.css`**: Replace `.card__caption` with `.home-body .cards-container li p` or `li > a > p` -- [ ] **`main.css`**: Replace `.card__media` with `.home-body figure` +- [x] **`main.css`**: Replace `.card__caption` with `.home-body .cards-container li p` or `li > a > p` +- [x] **`main.css`**: Replace `.card__media` with `.home-body figure` - [ ] **`tfe.css`**: Replace `.tfe-meta-list` selectors with `article dl`, `article dt`, `article dd` - [ ] **`tfe.css`**: Replace `.tfe-media-block` with `aside figure` - [ ] **`tfe.css`**: Replace `.tfe-file-caption` with `aside figcaption` @@ -22,10 +22,10 @@ ## Template HTML changes to match - [ ] In all admin templates, replace `

    ` with `` elements -- [ ] In `tfe.php`, remove `class="tfe-meta-list"` — target via `article dl` -- [ ] In `tfe.php`, remove `class="tfe-media-block"` — target via `aside figure` -- [ ] In `tfe.php`, remove `class="tfe-file-caption"` — target via `aside figcaption` -- [ ] In `index.php`, remove `class="card__caption"` — target via `li > a > p` +- [x] In `tfe.php`, remove `class="tfe-meta-list"` — target via `article dl` +- [x] In `tfe.php`, remove `class="tfe-media-block"` — target via `aside figure` +- [x] In `tfe.php`, remove `class="tfe-file-caption"` — target via `aside figcaption` +- [x] In `index.php`, remove `class="card__caption"` — target via `li > a > p` ## Scattered inline styles in templates @@ -43,7 +43,7 @@ - [x] **`index.php`**: Replace `

    ` with `