Files
xamxam/app/templates/admin/contenus.php
Pontoporeia 43702542eb feat(admin): sortable form-help blocks with two-panel UI
- Migration 005: add sort_order column to form_help_blocks
- Database: getAllFormHelpBlocks orders by sort_order; new reorderFormHelpBlocks()
- actions/form-help-reorder.php: HTMX POST handler, CSRF-validated, 204 response
- templates/admin/contenus.php: replace flat table with two-panel layout
  - Left: SortableJS 1.15.2 + htmx drag-and-drop ordered block cards
  - Right: static form structure reference showing fieldsets and their inputs
- admin.css: .fhb-* styles for layout, cards, ghost/chosen/drag states, anchors
- schema.sql: updated form_help_blocks DDL with sort_order column
2026-04-29 21:45:55 +02:00

227 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<main id="main-content">
<h1>Contenus</h1>
<?php
$flash = App::consumeFlash();
?>
<?php if ($flash['success']): ?>
<div class="flash-success" role="alert"><?= htmlspecialchars($flash['success']) ?></div>
<?php endif; ?>
<?php if ($flash['error']): ?>
<div class="flash-error" role="alert"><?= htmlspecialchars($flash['error']) ?></div>
<?php endif; ?>
<h2>Pages statiques</h2>
<table>
<thead>
<tr>
<th scope="col">Slug</th>
<th scope="col">Titre</th>
<th scope="col">Mis à jour</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($pages as $p): ?>
<tr>
<td><code><?= htmlspecialchars($p['slug']) ?></code></td>
<td><?= htmlspecialchars($p['title']) ?></td>
<td><?= htmlspecialchars($p['updated_at'] ?? '—') ?></td>
<td>
<a href="/admin/contenus-edit.php?slug=<?= urlencode($p['slug']) ?>"
class="admin-btn admin-btn--sm">Éditer</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h2 style="margin-top:2rem;">À propos</h2>
<table>
<thead>
<tr>
<th scope="col">Clé</th>
<th scope="col">Type</th>
<th scope="col">Mis à jour</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($aproposKeys as $a): ?>
<?php
$typeLabel = match($a['key']) {
'contacts' => 'Contacts',
'credits' => 'Crédits',
};
?>
<tr>
<td><code><?= htmlspecialchars($a['key']) ?></code></td>
<td><?= htmlspecialchars($typeLabel) ?></td>
<td><?= htmlspecialchars($a['updated_at'] ?? '—') ?></td>
<td>
<a href="/admin/contenus-edit.php?apropos=<?= urlencode($a['key']) ?>"
class="admin-btn admin-btn--sm">Éditer</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- ═══════════════════════════════════════════════════════════════════
Blocs d'aide du formulaire étudiant·e
═══════════════════════════════════════════════════════════════════ -->
<h2 id="form-help-blocks" style="margin-top:2rem;">Blocs d'aide du formulaire étudiant·e</h2>
<p>Ces textes apparaissent dans le formulaire de soumission accessible via les liens de partage.
Ils permettent d'expliquer aux étudiant·es comment remplir chaque section. Supporte le Markdown.</p>
<p class="fhb-hint">
<strong>Glissez</strong> les blocs d'aide (cartes violettes) pour les réorganiser dans le formulaire.
Cliquez sur <strong>Éditer</strong> pour modifier le contenu d'un bloc.
L'ordre est sauvegardé automatiquement après chaque déplacement.
</p>
<?php
// Build an ordered flat list of all blocks for the sortable form.
// $formHelpBlocks is keyed by block key, already sorted by sort_order.
$orderedBlocks = [];
foreach ($formHelpBlocks as $key => $block) {
$orderedBlocks[] = array_merge($block, [
'key' => $key,
'label' => Database::FORM_HELP_LABELS[$key] ?? $key,
]);
}
// Static form structure: each item is either a 'fieldset' (visual container)
// or an 'anchor' for a specific block key showing where it sits in the form.
// We also need a mapping from block key → where it currently sits in the sorted list.
// The entire sorted order is what matters; we render the form structure as a visual
// reference alongside the sortable list.
$formStructure = [
['type' => 'anchor', 'key' => 'partage_intro', 'position' => 'before-form', 'label' => 'Avant le formulaire (introduction)'],
['type' => 'fieldset', 'name' => 'Informations du TFE', 'inputs' => ['Titre', 'Sous-titre', 'Auteur·ice(s)', 'Contact', 'Synopsis']],
['type' => 'anchor', 'key' => 'fieldset_tfe_info', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Informations du TFE »'],
['type' => 'anchor', 'key' => 'fieldset_synopsis', 'position' => 'intro-fieldset', 'label' => 'Note sous le champ Synopsis'],
['type' => 'fieldset', 'name' => 'Composition du jury', 'inputs' => ['Président·e', 'Promoteur·ice', 'Lecteur·ices (×4)']],
['type' => 'anchor', 'key' => 'fieldset_jury', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Jury »'],
['type' => 'fieldset', 'name' => 'Cadre académique', 'inputs' => ['Année', 'Orientation', 'AP', 'Finalité', 'Langues', 'Formats', 'Mots-clés']],
['type' => 'anchor', 'key' => 'fieldset_academic', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Cadre académique »'],
['type' => 'fieldset', 'name' => 'Fichiers', 'inputs' => ['Fichier principal (PDF)', 'Annexes']],
['type' => 'anchor', 'key' => 'fieldset_files', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Fichiers »'],
['type' => 'fieldset', 'name' => 'Visibilité / Accès', 'inputs' => ["Type d'accès", 'Licence']],
['type' => 'anchor', 'key' => 'fieldset_access', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « Visibilité »'],
['type' => 'fieldset', 'name' => 'E-mail de confirmation', 'inputs' => ['Adresse e-mail']],
['type' => 'anchor', 'key' => 'fieldset_email', 'position' => 'intro-fieldset', 'label' => 'Intro du fieldset « E-mail »'],
];
?>
<div class="fhb-layout">
<!-- Left: sortable ordered list of help blocks -->
<div class="fhb-sortable-panel">
<h3 class="fhb-panel-title">Ordre des blocs</h3>
<p class="fhb-panel-desc">Glissez pour réorganiser</p>
<form class="fhb-sortable sortable"
hx-post="/admin/actions/form-help-reorder.php"
hx-trigger="end"
hx-include="this"
hx-swap="none">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<div class="htmx-indicator fhb-saving">Sauvegarde…</div>
<?php foreach ($orderedBlocks as $b): ?>
<div class="fhb-block-card" data-key="<?= htmlspecialchars($b['key']) ?>">
<input type="hidden" name="block[]" value="<?= htmlspecialchars($b['key']) ?>">
<span class="fhb-drag-handle" aria-hidden="true">⠿</span>
<div class="fhb-block-info">
<span class="fhb-block-label"><?= htmlspecialchars($b['label']) ?></span>
<?php if (trim($b['content']) !== ''): ?>
<span class="fhb-block-preview"><?= htmlspecialchars(mb_strimwidth(trim($b['content']), 0, 60, '…')) ?></span>
<?php else: ?>
<span class="fhb-block-empty">— vide —</span>
<?php endif; ?>
</div>
<a href="/admin/contenus-edit.php?form_block=<?= urlencode($b['key']) ?>"
class="admin-btn admin-btn--sm fhb-edit-btn">Éditer</a>
</div>
<?php endforeach; ?>
</form>
</div>
<!-- Right: static form structure reference -->
<div class="fhb-form-preview-panel">
<h3 class="fhb-panel-title">Structure du formulaire</h3>
<p class="fhb-panel-desc">Référence visuelle — non modifiable</p>
<div class="fhb-form-preview">
<?php foreach ($formStructure as $item): ?>
<?php if ($item['type'] === 'anchor'): ?>
<?php
$bData = $formHelpBlocks[$item['key']] ?? ['content' => '', 'sort_order' => 99];
$hasContent = trim($bData['content'] ?? '') !== '';
?>
<div class="fhb-anchor <?= $hasContent ? 'fhb-anchor--filled' : 'fhb-anchor--empty' ?>">
<span class="fhb-anchor-icon"><?= $hasContent ? '✎' : '○' ?></span>
<span class="fhb-anchor-label"><?= htmlspecialchars(Database::FORM_HELP_LABELS[$item['key']] ?? $item['key']) ?></span>
<?php if ($hasContent): ?>
<span class="fhb-anchor-pos">#<?= (int)$bData['sort_order'] + 1 ?></span>
<?php endif; ?>
</div>
<?php elseif ($item['type'] === 'fieldset'): ?>
<div class="fhb-fieldset-preview">
<div class="fhb-fieldset-legend"><?= htmlspecialchars($item['name']) ?></div>
<ul class="fhb-fieldset-inputs">
<?php foreach ($item['inputs'] as $inp): ?>
<li><?= htmlspecialchars($inp) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
</div><!-- /.fhb-layout -->
</main>
<script>
(function () {
var script = document.createElement('script');
script.src = '<?= App::assetV('/assets/js/sortable.min.js') ?>';
script.onload = initSortable;
document.head.appendChild(script);
function initSortable() {
htmx.onLoad(function (content) {
var sortables = content.querySelectorAll('.sortable');
for (var i = 0; i < sortables.length; i++) {
(function (sortable) {
var sortableInstance = new Sortable(sortable, {
animation: 150,
handle: '.fhb-drag-handle',
ghostClass: 'fhb-ghost',
chosenClass: 'fhb-chosen',
dragClass: 'fhb-dragging',
filter: '.htmx-indicator',
onMove: function (evt) {
return evt.related.className.indexOf('htmx-indicator') === -1;
},
onEnd: function () {
this.option('disabled', true);
}
});
sortable.addEventListener('htmx:afterRequest', function () {
sortableInstance.option('disabled', false);
});
})(sortables[i]);
}
});
}
})();
</script>