feat: jury composition + banner image upload

- migration 004: thesis_supervisors.role + is_external; view adds jury_president/jury_promoteurs/jury_lecteurs
- migration 005: theses.banner_path; view exposes t.banner_path and t.license_id
- Database: getThesisJury(), setThesisJury(), setBannerPath()
- admin/add.php: jury fieldset (président/promoteur/lecteurs + externe checkboxes, JS add/remove rows); banner file input
- admin/edit.php: jury fieldset pre-populated from DB; banner preview + remove checkbox + upload; multipart form
- admin/actions/formulaire.php: parse jury fields → setThesisJury(); banner upload to banners/
- tfe.php: three conditional jury rows (président·e, promoteur·ice, lecteur·ices)
- schema.sql: updated thesis_supervisors, theses, v_theses_full, v_theses_public definitions
- admin.css: fieldset, jury-row, jury-entry, btn-remove styles
This commit is contained in:
Pontoporeia
2026-03-24 13:25:23 +01:00
parent d87348c388
commit cefceb046c
12 changed files with 569 additions and 138 deletions

View File

@@ -79,19 +79,67 @@ function wasSelected($key, $value) {
value="<?= old('mail') ?>">
</div>
<!-- Promoteur interne -->
<div class="admin-form-row">
<label class="admin-label" for="promoteurice">Promoteur·ice interne :</label>
<input class="admin-input" type="text" id="promoteurice" name="promoteurice"
value="<?= old('promoteurice') ?>">
</div>
<!-- Composition du jury -->
<fieldset class="admin-fieldset">
<legend class="admin-fieldset-legend">Composition du jury</legend>
<!-- Promoteur externe -->
<div class="admin-form-row">
<label class="admin-label" for="promoteurice_externe">Promoteur·ice externe :</label>
<input class="admin-input" type="text" id="promoteurice_externe" name="promoteurice_externe"
value="<?= old('promoteurice_externe') ?>">
</div>
<!-- Président·e -->
<div class="admin-form-row">
<label class="admin-label" for="jury_president">Président·e :</label>
<input class="admin-input" type="text" id="jury_president" name="jury_president"
value="<?= old('jury_president') ?>"
placeholder="Nom du/de la président·e (interne)">
</div>
<!-- Promoteur·ice -->
<div class="admin-form-row">
<label class="admin-label" for="jury_promoteur">Promoteur·ice :</label>
<div class="admin-jury-row">
<input class="admin-input" type="text" id="jury_promoteur" name="jury_promoteur"
value="<?= old('jury_promoteur') ?>" placeholder="Nom">
<label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_promoteur_ext" value="1"
<?= wasSelected('jury_promoteur_ext', '1') ? 'checked' : '' ?>>
Externe
</label>
</div>
</div>
<!-- Lecteur·ices (dynamic) -->
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label">Lecteur·ices :</label>
<div id="jury-lecteurs-list" class="admin-jury-list">
<!-- rows injected by JS; start with one empty row -->
<div class="admin-jury-entry">
<input class="admin-input" type="text" name="jury_lecteurs[]" placeholder="Nom">
<label class="admin-checkbox-label admin-jury-ext">
<input type="checkbox" name="jury_lecteurs_ext[0]" value="1"> Externe
</label>
<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>
</div>
</div>
<button type="button" class="admin-btn-secondary" style="margin-top:.5rem;"
onclick="addJuryRow()">+ Ajouter un·e lecteur·ice</button>
</div>
</fieldset>
<script>
var juryIdx = 1;
function addJuryRow() {
var list = document.getElementById('jury-lecteurs-list');
var div = document.createElement('div');
div.className = 'admin-jury-entry';
div.innerHTML = '<input class="admin-input" type="text" name="jury_lecteurs[]" placeholder="Nom">'
+ '<label class="admin-checkbox-label admin-jury-ext">'
+ '<input type="checkbox" name="jury_lecteurs_ext[' + juryIdx + ']" value="1"> Externe'
+ '</label>'
+ '<button type="button" class="admin-btn-remove" onclick="removeJuryRow(this)">✕</button>';
list.appendChild(div);
juryIdx++;
}
function removeJuryRow(btn) {
btn.closest('.admin-jury-entry').remove();
}
</script>
<!-- Année -->
<div class="admin-form-row">
@@ -234,6 +282,15 @@ function wasSelected($key, $value) {
</div>
</div>
<!-- Image bannière -->
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label">Image bannière (accueil) :</label>
<div class="admin-file-input">
<input type="file" id="banner" name="banner" accept="image/jpeg,image/png,image/webp">
<p class="admin-hint">JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 5 MB.</p>
</div>
</div>
<!-- Fichiers -->
<div class="admin-form-row" style="align-items:start;">
<label class="admin-label">Fichiers du TFE :</label>