mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Add Mots-clés and Langues management to contenus page
- Add searchLanguages, getAllLanguagesWithCount, renameLanguage, mergeLanguage, deleteLanguage to Database - Create actions/language.php handler with rename/merge/merge_bulk/delete actions - Add merge_bulk action to actions/tag.php - Add Mots-clés section to contenus template with HTMX search, select checkboxes, rename/delete/merge buttons, and multi-select merge toolbar - Add Langues section to contenus template with same pattern - Create contenus-tags-fragment.php and contenus-languages-fragment.php HTMX fragments - Remove form-settings- from flat-fieldset CSS selector so fieldsets in contenus retain border/padding - contenus.php: add 'Gérer les mots-clés' link to /admin/tags.php - contenus.php: add Langues fieldset with HTMX search + table (rename/merge/delete/bulk) - tags.php: add HTMX search bar, checkbox column, bulk merge toolbar - Create tags-fragment.php and contenus-langues-fragment.php for HTMX - Remove tab component and associated CSS - Simplify JS: separate tags/langues-prefixed functions - Fix redirects: tag.php defaults to /admin/tags.php, supports return override - Keep tags.php standalone page and Mots-clés button unchanged
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
<div class="admin-with-toc">
|
||||
<?php include APP_ROOT . '/templates/admin/partials/admin-toc.php'; ?>
|
||||
<main id="main-content">
|
||||
<h1>Contenus</h1>
|
||||
|
||||
@@ -44,6 +46,35 @@
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════════
|
||||
DONNÉES SECONDAIRES
|
||||
═══════════════════════════════════════════════════════════════════ -->
|
||||
<section aria-labelledby="donnees-secondaires-title">
|
||||
<h2 id="donnees-secondaires-title">Données Secondaires</h2>
|
||||
|
||||
<!-- ── Mots-clés ── -->
|
||||
<p><a href="/admin/tags.php" class="btn btn--sm btn--primary">Gérer les mots-clés</a></p>
|
||||
|
||||
<!-- ── Langues ── -->
|
||||
<fieldset>
|
||||
<legend>Langues</legend>
|
||||
|
||||
<form id="langues-search-form"
|
||||
hx-get="/admin/contenus-langues-fragment.php"
|
||||
hx-target="#langues-table-wrap"
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="input changed delay:200ms from:input[name=q], keyup[key=='Enter'] from:input[name=q]"
|
||||
hx-push-url="false"
|
||||
style="margin-bottom:var(--space-xs)">
|
||||
<input type="text" name="q" placeholder="Rechercher une langue…" style="max-width:300px">
|
||||
</form>
|
||||
|
||||
<div id="langues-table-wrap" hx-get="/admin/contenus-langues-fragment.php" hx-trigger="load" hx-swap="innerHTML">
|
||||
<!-- populated by HTMX -->
|
||||
</div>
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════════
|
||||
PARAMÈTRES DU FORMULAIRE
|
||||
═══════════════════════════════════════════════════════════════════ -->
|
||||
@@ -163,44 +194,22 @@
|
||||
<?php
|
||||
$blocks = $formHelpBlocks;
|
||||
|
||||
// ── Student form structure — each help block above its fieldset ───────────
|
||||
// Pairs: [help_key, fieldset_name, fieldset_inputs]
|
||||
$pairs = [
|
||||
// Top of form
|
||||
['partage_intro', null, null],
|
||||
|
||||
// Informations du TFE
|
||||
['fieldset_tfe_info', 'Informations du TFE',
|
||||
['Titre', 'Sous-titre', 'Auteur·ice(s)', 'Contact visible', 'Synopsis']],
|
||||
|
||||
// Langue(s)
|
||||
['fieldset_languages', 'Langue(s)',
|
||||
['Langues du TFE (cases à cocher)', 'Autre(s) langue(s)']],
|
||||
|
||||
// Mots-clés
|
||||
['fieldset_keywords', 'Mots-clés',
|
||||
['Mots-clés (max 10), séparés par des virgules']],
|
||||
|
||||
// Cadre académique
|
||||
['fieldset_academic', 'Cadre académique',
|
||||
['Année', 'Orientation', 'AP', 'Finalité']],
|
||||
|
||||
// Composition du jury
|
||||
['fieldset_jury', 'Composition du jury',
|
||||
['Président·e', 'Promoteur·ice(s)', 'Lecteur·ices']],
|
||||
|
||||
// Format(s) + Fichiers
|
||||
['fieldset_files', 'Format(s) + Fichiers',
|
||||
['Formats (PDF, vidéo, audio, site web…)', 'Couverture', 'Note d\'intention', 'Fichier principal', 'Annexes']],
|
||||
|
||||
// Métadonnées complémentaires (supprimé)
|
||||
// Ces champs sont redondants avec les fichiers attachés
|
||||
|
||||
// Degrés d'ouverture et licences
|
||||
['fieldset_access', 'Degrés d\'ouverture et licences',
|
||||
['Généralités', 'Degré (libre/interne/interdit)', 'Licence', 'CC2r']],
|
||||
|
||||
// E-mail de confirmation
|
||||
['fieldset_email', 'E-mail de confirmation',
|
||||
['Adresse e-mail']],
|
||||
];
|
||||
@@ -208,7 +217,6 @@
|
||||
|
||||
<div class="fhb-structure">
|
||||
<?php foreach ($pairs as [$helpKey, $fieldsetName, $inputs]):
|
||||
// Help block
|
||||
$b = $blocks[$helpKey] ?? ['content' => '', 'name' => '', 'enabled' => 0];
|
||||
$title = $b['name'] ?: ($fieldsetName ?? $helpKey);
|
||||
?>
|
||||
@@ -239,6 +247,131 @@
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
CONFIRM DIALOGS FOR LANGUES
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
|
||||
<dialog id="langues-merge-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="langues-merge-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h2 id="langues-merge-title">Fusionner la langue</h2>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="this.closest('dialog').close()">✕</button>
|
||||
</div>
|
||||
<div class="admin-dialog__alert">
|
||||
<p>Fusionner dans « <strong id="langues-merge-target-name"></strong> » ? La langue source sera supprimée.</p>
|
||||
</div>
|
||||
<div class="admin-dialog__footer">
|
||||
<button type="button" class="btn btn--warning" onclick="this.closest('dialog').close(); languesSubmitPending()">Fusionner</button>
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="langues-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="langues-delete-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h2 id="langues-delete-title">Supprimer la langue</h2>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="this.closest('dialog').close()">✕</button>
|
||||
</div>
|
||||
<div class="admin-dialog__alert">
|
||||
<p>Supprimer « <strong id="langues-delete-name"></strong> » ? Cette action est irréversible.</p>
|
||||
</div>
|
||||
<div class="admin-dialog__footer">
|
||||
<button type="button" class="btn btn--danger" onclick="this.closest('dialog').close(); languesSubmitPending()">Supprimer</button>
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="langues-bulk-merge-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="langues-bulk-merge-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h2 id="langues-bulk-merge-title">Fusionner des langues</h2>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="this.closest('dialog').close()">✕</button>
|
||||
</div>
|
||||
<div class="admin-dialog__alert">
|
||||
<p>Fusionner <strong><span id="langues-bulk-merge-count"></span> langue(s)</strong> sélectionnée(s) dans :</p>
|
||||
<select id="langues-bulk-merge-target-select" class="admin-select--inline" style="margin-top:var(--space-xs); width:100%" required>
|
||||
<option value="">— Choisir la destination —</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="admin-dialog__footer">
|
||||
<button type="button" class="btn btn--warning" onclick="languesExecBulkMerge()">Fusionner</button>
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
let _languesPendingForm = null;
|
||||
|
||||
function languesConfirmMerge(btn) {
|
||||
const form = btn.closest('form');
|
||||
const select = form.querySelector('select[name="target_id"]');
|
||||
if (!select.value) return true;
|
||||
_languesPendingForm = form;
|
||||
document.getElementById('langues-merge-target-name').textContent = select.options[select.selectedIndex]?.text ?? '';
|
||||
document.getElementById('langues-merge-dialog').showModal();
|
||||
return false;
|
||||
}
|
||||
|
||||
function languesConfirmDelete(btn, name) {
|
||||
_languesPendingForm = btn.closest('form');
|
||||
document.getElementById('langues-delete-name').textContent = name;
|
||||
document.getElementById('langues-delete-dialog').showModal();
|
||||
}
|
||||
|
||||
function languesSubmitPending() {
|
||||
if (_languesPendingForm) _languesPendingForm.submit();
|
||||
}
|
||||
|
||||
function languesToggleAll(src) {
|
||||
document.querySelectorAll('input[name="selected_langs[]"]').forEach(cb => cb.checked = src.checked);
|
||||
languesUpdateBulk();
|
||||
}
|
||||
|
||||
function languesUpdateBulk() {
|
||||
const n = document.querySelectorAll('input[name="selected_langs[]"]:checked').length;
|
||||
document.getElementById('langues-selected-count').textContent = n;
|
||||
document.getElementById('langues-bulk-actions').style.display = n > 1 ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function languesConfirmBulkMerge() {
|
||||
const checked = document.querySelectorAll('input[name="selected_langs[]"]:checked');
|
||||
if (checked.length < 2) return;
|
||||
document.getElementById('langues-bulk-merge-count').textContent = checked.length;
|
||||
const sel = document.getElementById('langues-bulk-merge-target-select');
|
||||
sel.innerHTML = '<option value="">— Choisir la destination —</option>';
|
||||
checked.forEach(cb => {
|
||||
const tr = cb.closest('tr');
|
||||
sel.innerHTML += '<option value="' + cb.value + '">' + tr.querySelector('td:nth-child(2)').textContent.trim() + '</option>';
|
||||
});
|
||||
document.getElementById('langues-bulk-merge-dialog').showModal();
|
||||
}
|
||||
|
||||
function languesExecBulkMerge() {
|
||||
const targetId = document.getElementById('langues-bulk-merge-target-select').value;
|
||||
if (!targetId) return;
|
||||
document.getElementById('langues-bulk-target').value = targetId;
|
||||
const container = document.getElementById('langues-bulk-checkboxes');
|
||||
container.innerHTML = '';
|
||||
document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach(cb => {
|
||||
const inp = document.createElement('input');
|
||||
inp.type = 'hidden';
|
||||
inp.name = 'selected_langs[]';
|
||||
inp.value = cb.value;
|
||||
container.appendChild(inp);
|
||||
});
|
||||
document.getElementById('langues-bulk-merge-dialog').close();
|
||||
document.getElementById('langues-bulk-form').submit();
|
||||
}
|
||||
|
||||
document.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.target.id === 'langues-table-wrap') {
|
||||
document.querySelectorAll('input[name="selected_langs[]"]').forEach(cb => cb.addEventListener('change', languesUpdateBulk));
|
||||
languesUpdateBulk();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
|
||||
Reference in New Issue
Block a user