mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-11-09 19:20:09 +01:00
perf(admin): infinite-scroll the contenus langues/mots-clés tables
The contenus page loaded both lookup tables in full via HTMX on page load: 760 tag rows + 216 language rows, ~2.4MB of HTML including ~1960 inline SVG icons and per-row CSRF forms. The DB queries were already fast (~6ms); the cost was pure client-side payload and DOM. - Add paged lookups: getTagsPage/getLanguagesPage + countTagsWithCount/ countLanguagesWithCount, and optional limit/offset on the existing unpaged variants (backward compatible). - Fragments serve 25 rows per request. A sentinel <tr> with hx-trigger="intersect once root:#<wrap>" appends the next page when it scrolls into the table's own scroll container (htmx 'revealed' only checks the window viewport, not nested scrollers). - Search forms still swap the whole wrapper and reset to offset 0. - Style the load-more row; add PagedLanguagesTagsTest coverage. Initial DOM for the page drops from ~2.4MB to ~160KB; scrolling reaches the full totals (736 tags, 216 languages) and stops cleanly.
This commit is contained in:
1 parent
8016712741
commit
25c5133086
6 files changed
+475
-62
No files matched your search
+118
-8
@@ -1390,11 +1390,41 @@ class Database
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all tags with a count of associated (published) theses.
|
||||
* Paged tag listing used by the admin contenus "load more" fragment.
|
||||
* Same ordering as getAllTagsWithCount(); $query filters by name prefix.
|
||||
*/
|
||||
public function getAllTagsWithCount(): array
|
||||
public function getTagsPage(string $query = '', int $limit = 100, int $offset = 0): array
|
||||
{
|
||||
$stmt = $this->pdo->query('
|
||||
$query = trim($query);
|
||||
$sql = '
|
||||
SELECT tg.id, tg.name,
|
||||
COUNT(DISTINCT t.id) as thesis_count
|
||||
FROM tags tg
|
||||
LEFT JOIN thesis_tags tt ON tg.id = tt.tag_id
|
||||
LEFT JOIN theses t ON tt.thesis_id = t.id AND t.deleted_at IS NULL
|
||||
WHERE tg.deleted_at IS NULL';
|
||||
$params = [];
|
||||
if ($query !== '') {
|
||||
$sql .= ' AND tg.name LIKE ?';
|
||||
$params[] = $query . '%';
|
||||
}
|
||||
$sql .= ' GROUP BY tg.id ORDER BY tg.name COLLATE NOCASE LIMIT ? OFFSET ?';
|
||||
$params[] = $limit;
|
||||
$params[] = $offset;
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all tags with a count of associated (published) theses.
|
||||
*
|
||||
* @param int|null $limit Optional page size (for htmx infinite scroll).
|
||||
* @param int $offset Row offset when $limit is set.
|
||||
*/
|
||||
public function getAllTagsWithCount(?int $limit = null, int $offset = 0): array
|
||||
{
|
||||
$sql = '
|
||||
SELECT tg.id, tg.name,
|
||||
COUNT(DISTINCT t.id) as thesis_count
|
||||
FROM tags tg
|
||||
@@ -1403,8 +1433,32 @@ class Database
|
||||
WHERE tg.deleted_at IS NULL
|
||||
GROUP BY tg.id
|
||||
ORDER BY tg.name COLLATE NOCASE
|
||||
');
|
||||
return $stmt->fetchAll();
|
||||
';
|
||||
if ($limit !== null) {
|
||||
$stmt = $this->pdo->prepare($sql . ' LIMIT ? OFFSET ?');
|
||||
$stmt->execute([$limit, $offset]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql)->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count tags matching an optional name prefix (used to know when the
|
||||
* infinite-scroll page has reached the end).
|
||||
*/
|
||||
public function countTagsWithCount(string $query = ''): int
|
||||
{
|
||||
$query = trim($query);
|
||||
if ($query === '') {
|
||||
return (int) $this->pdo->query(
|
||||
'SELECT COUNT(*) FROM tags WHERE deleted_at IS NULL'
|
||||
)->fetchColumn();
|
||||
}
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT COUNT(*) FROM tags WHERE name LIKE ? AND deleted_at IS NULL'
|
||||
);
|
||||
$stmt->execute([$query . '%']);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1514,11 +1568,14 @@ class Database
|
||||
|
||||
/**
|
||||
* Return all languages with a count of associated theses.
|
||||
*
|
||||
* @param int|null $limit Optional page size (for htmx infinite scroll).
|
||||
* @param int $offset Row offset when $limit is set.
|
||||
*/
|
||||
public function getAllLanguagesWithCount(): array
|
||||
public function getAllLanguagesWithCount(?int $limit = null, int $offset = 0): array
|
||||
{
|
||||
// Group by lowercased name to deduplicate, keeping the first id
|
||||
$stmt = $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT MIN(l.id) as id,
|
||||
UPPER(SUBSTR(MIN(l.name),1,1)) || SUBSTR(MIN(l.name),2) as name,
|
||||
COUNT(DISTINCT t.id) as thesis_count
|
||||
@@ -1528,7 +1585,60 @@ class Database
|
||||
WHERE l.deleted_at IS NULL
|
||||
GROUP BY LOWER(l.name)
|
||||
ORDER BY LOWER(MIN(l.name)) COLLATE NOCASE
|
||||
');
|
||||
';
|
||||
if ($limit !== null) {
|
||||
$stmt = $this->pdo->prepare($sql . ' LIMIT ? OFFSET ?');
|
||||
$stmt->execute([$limit, $offset]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql)->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count distinct languages matching an optional name prefix (used to know
|
||||
* when the infinite-scroll page has reached the end).
|
||||
*/
|
||||
public function countLanguagesWithCount(string $query = ''): int
|
||||
{
|
||||
$query = trim($query);
|
||||
if ($query === '') {
|
||||
return (int) $this->pdo->query(
|
||||
'SELECT COUNT(DISTINCT LOWER(name)) FROM languages WHERE deleted_at IS NULL'
|
||||
)->fetchColumn();
|
||||
}
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT COUNT(DISTINCT LOWER(name)) FROM languages WHERE LOWER(name) LIKE LOWER(?) AND deleted_at IS NULL'
|
||||
);
|
||||
$stmt->execute([$query . '%']);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Paged language listing used by the admin contenus "load more" fragment.
|
||||
* Same ordering/dedup as getAllLanguagesWithCount(); $query filters by
|
||||
* name prefix.
|
||||
*/
|
||||
public function getLanguagesPage(string $query = '', int $limit = 100, int $offset = 0): array
|
||||
{
|
||||
$query = trim($query);
|
||||
$sql = '
|
||||
SELECT MIN(l.id) as id,
|
||||
UPPER(SUBSTR(MIN(l.name),1,1)) || SUBSTR(MIN(l.name),2) as name,
|
||||
COUNT(DISTINCT t.id) as thesis_count
|
||||
FROM languages l
|
||||
LEFT JOIN thesis_languages tl ON l.id = tl.language_id
|
||||
LEFT JOIN theses t ON tl.thesis_id = t.id AND t.deleted_at IS NULL
|
||||
WHERE l.deleted_at IS NULL';
|
||||
$params = [];
|
||||
if ($query !== '') {
|
||||
$sql .= ' AND LOWER(l.name) LIKE LOWER(?)';
|
||||
$params[] = $query . '%';
|
||||
}
|
||||
$sql .= ' GROUP BY LOWER(l.name) ORDER BY LOWER(MIN(l.name)) COLLATE NOCASE LIMIT ? OFFSET ?';
|
||||
$params[] = $limit;
|
||||
$params[] = $offset;
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user