Files
xamxam/app/templates/admin/index.php
Pontoporeia ae6d9b86b3 Replace browser alert/confirm dialogs with <dialog> modals
- admin/index.php: alert() → no-selection dialog; confirm() bulk actions → bulk-confirm/bulk-delete dialogs; confirm() single delete → delete-thesis dialog; removed redundant confirm on Dépublier (reversible action)
- admin/tags.php: confirm() merge/delete → merge-tag/delete-tag dialogs
- admin/acces-etudiante.php: confirm() delete link → delete-link dialog
- admin/acces.php: confirm() archive link → archive-link dialog
- admin/parametres.php: confirm() maintenance/delete-all → enable-maintenance/delete-all-tfe dialogs; admin password confirm() kept with TODO comment
- admin/account.php: admin password confirm() kept with TODO comment
- admin.css: add .admin-dialog--sm, .admin-dialog__alert, .admin-dialog__footer styles
2026-05-05 11:04:52 +02:00

400 lines
20 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
function toggleAll(src) {
document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.checked = src.checked);
updateBulk();
}
function updateBulk() {
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
const bulk = document.getElementById('bulk-actions');
document.getElementById('selected-count').textContent = checked.length;
bulk.style.display = checked.length > 0 ? 'flex' : 'none';
}
// Pending bulk action state
let _pendingBulkAction = null;
function bulkAction(action) {
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
if (!checked.length) {
document.getElementById('no-selection-dialog').showModal();
return;
}
_pendingBulkAction = action;
let word, endpoint;
if (action === 'publish') { word = 'publier'; endpoint = 'actions/publish.php'; }
else if (action === 'unpublish') { word = 'dépublier'; endpoint = 'actions/publish.php'; }
else if (action === 'delete') { word = 'supprimer'; endpoint = 'actions/delete.php'; }
else return;
if (action === 'delete') {
document.getElementById('bulk-delete-count').textContent = checked.length;
document.getElementById('bulk-delete-dialog').showModal();
} else {
document.getElementById('bulk-confirm-word').textContent = word.charAt(0).toUpperCase() + word.slice(1);
document.getElementById('bulk-confirm-count').textContent = checked.length;
document.getElementById('bulk-confirm-dialog').showModal();
}
}
function _executeBulkAction() {
const action = _pendingBulkAction;
if (!action) return;
let endpoint;
if (action === 'publish' || action === 'unpublish') { endpoint = 'actions/publish.php'; }
else if (action === 'delete') { endpoint = 'actions/delete.php'; }
else return;
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
document.getElementById('bulk-action-input').value = action;
document.getElementById('bulk-form').action = endpoint;
const container = document.getElementById('bulk-checkboxes');
container.innerHTML = '';
checked.forEach(cb => {
const inp = document.createElement('input');
inp.type = 'hidden'; inp.name = 'selected_theses[]'; inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('bulk-form').submit();
}
// Pending single-delete state
let _pendingDeleteId = null;
function deleteThesis(id, title) {
_pendingDeleteId = id;
document.getElementById('delete-thesis-title').textContent = title;
document.getElementById('delete-thesis-dialog').showModal();
}
function _executeDeleteThesis() {
const form = document.getElementById('delete-form-' + _pendingDeleteId);
if (form) form.submit();
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.addEventListener('change', updateBulk));
});
</script>
<main id="main-content">
<!-- Title + filters + stats + import all in one toolbar row -->
<div class="admin-list-toolbar">
<h1>Liste des TFE</h1>
<form class="admin-filters" method="get" action="/admin/">
<input type="text" name="search" placeholder="Titre, auteur..."
value="<?= htmlspecialchars($searchQuery) ?>">
<select name="year">
<option value="">Année</option>
<?php foreach ($years as $y): ?>
<option value="<?= $y ?>" <?= $yearFilter == $y ? 'selected' : '' ?>><?= $y ?></option>
<?php endforeach; ?>
</select>
<select name="orientation">
<option value="">Orientation</option>
<?php foreach ($orientations as $o): ?>
<option value="<?= $o['id'] ?>" <?= $orientationFilter == $o['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($o['name']) ?>
</option>
<?php endforeach; ?>
</select>
<select name="ap">
<option value="">AP</option>
<?php foreach ($apPrograms as $ap): ?>
<option value="<?= $ap['id'] ?>" <?= $apFilter == $ap['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($ap['name']) ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="admin-filters-btn">Filtrer</button>
<?php if ($searchQuery || $yearFilter || $orientationFilter || $apFilter): ?>
<button type="button" class="admin-filters-reset"
onclick="window.location='/admin/'">&#x2715; Réinitialiser</button>
<?php endif; ?>
</form>
<div class="admin-list-toolbar__right">
<dl class="admin-stats">
<div class="admin-stat">
<dt class="admin-stat__label">Total</dt>
<dd class="admin-stat__number"><?= $stats['total'] ?></dd>
</div>
<div class="admin-stat">
<dt class="admin-stat__label">Publiés</dt>
<dd class="admin-stat__number"><?= $stats['published'] ?></dd>
</div>
<div class="admin-stat">
<dt class="admin-stat__label">Attente</dt>
<dd class="admin-stat__number"><?= $stats['pending'] ?></dd>
</div>
</dl>
<a href="/admin/add.php" class="admin-btn admin-btn--sm">Ajouter un TFE</a>
<button type="button" class="admin-btn admin-btn--sm" id="import-dialog-btn"
onclick="document.getElementById('import-dialog').showModal()">
Importer un CSV
</button>
<a href="/admin/actions/export-csv.php" class="admin-btn admin-btn--sm">
Exporter CSV
</a>
</div>
</div>
<!-- Bulk actions bar -->
<div id="bulk-actions" class="admin-bulk-actions" role="toolbar" aria-label="Actions groupées">
<strong><span id="selected-count">0</span> TFE(s) sélectionné(s)</strong>
<div class="admin-bulk-btns">
<button type="button" class="admin-btn-sm admin-btn-publish" onclick="bulkAction('publish')">Publier</button>
<button type="button" class="admin-btn-sm admin-btn-unpublish" onclick="bulkAction('unpublish')">Dépublier</button>
<button type="button" class="admin-btn-sm admin-btn-delete" onclick="bulkAction('delete')">Supprimer</button>
</div>
</div>
<form id="bulk-form" method="post" action="actions/publish.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" id="bulk-action-input" name="action" value="">
<input type="hidden" name="bulk" value="1">
<div id="bulk-checkboxes"></div>
</form>
<!-- Table -->
<?php if (empty($theses)): ?>
<p class="admin-empty">Aucun TFE trouvé.</p>
<?php else: ?>
<p class="admin-list-meta">
<?php
$from = $offset + 1;
$to = min($offset + $perPage, $totalCount);
if ($totalPages > 1) {
echo "{$from}-{$to} sur {$totalCount} TFE";
} else {
echo "$totalCount TFE";
}
?>
</p>
<?php
$sortParams = array_filter([
'search' => $searchQuery,
'year' => $yearFilter ?: '',
'orientation' => $orientationFilter ?: '',
'ap' => $apFilter ?: '',
]);
$sortLink = function(string $col) use ($sortCol, $sortDir, $sortParams): string {
$params = $sortParams;
$params['sort'] = $col;
$params['dir'] = ($sortCol === $col && $sortDir === 'desc') ? 'asc' : 'desc';
return '/admin/?' . http_build_query($params);
};
$sortArrow = function(string $col) use ($sortCol, $sortDir): string {
if ($sortCol !== $col) return '';
return $sortDir === 'asc' ? ' ↑' : ' ↓';
};
?>
<table>
<thead>
<tr>
<th scope="col"><input type="checkbox" onchange="toggleAll(this)"></th>
<th scope="col"><a href="<?= $sortLink('identifier') ?>" class="admin-sort-link">ID<?= $sortArrow('identifier') ?></a></th>
<th scope="col"><a href="<?= $sortLink('title') ?>" class="admin-sort-link">Titre<?= $sortArrow('title') ?></a></th>
<th scope="col">Auteur(s)</th>
<th scope="col"><a href="<?= $sortLink('year') ?>" class="admin-sort-link">Année<?= $sortArrow('year') ?></a></th>
<th scope="col"><a href="<?= $sortLink('orientation') ?>" class="admin-sort-link">Orientation<?= $sortArrow('orientation') ?></a></th>
<th scope="col"><a href="<?= $sortLink('ap_program') ?>" class="admin-sort-link">AP<?= $sortArrow('ap_program') ?></a></th>
<th scope="col"><a href="<?= $sortLink('is_published') ?>" class="admin-sort-link">Statut<?= $sortArrow('is_published') ?></a></th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($theses as $thesis): ?>
<tr>
<td><input type="checkbox" name="selected_theses[]" value="<?= $thesis['id'] ?>"></td>
<td class="admin-table-id"><?= htmlspecialchars($thesis['identifier'] ?? $thesis['id']) ?></td>
<td>
<div class="thesis-title"><?= htmlspecialchars($thesis['title']) ?></div>
<?php if ($thesis['subtitle']): ?>
<div class="thesis-subtitle"><?= htmlspecialchars($thesis['subtitle']) ?></div>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($thesis['authors'] ?? 'N/A') ?></td>
<td><?= $thesis['year'] ?></td>
<td><?= htmlspecialchars($thesis['orientation'] ?? 'N/A') ?></td>
<td><?= htmlspecialchars($thesis['ap_program'] ?? 'N/A') ?></td>
<td>
<?php $badgeType = 'publish'; $badgeValue = $thesis['is_published']; include APP_ROOT . '/templates/partials/status-badge.php'; ?>
<?php if (!empty($thesis['access_type'])): ?>
<br><?php $badgeType = 'access'; $badgeValue = $thesis['access_type']; include APP_ROOT . '/templates/partials/status-badge.php'; ?>
<?php endif; ?>
</td>
<td>
<div class="admin-actions">
<a href="/admin/recapitulatif.php?id=<?= $thesis['id'] ?>" class="admin-btn-sm admin-btn-view">Voir</a>
<a href="/admin/edit.php?id=<?= $thesis['id'] ?>" class="admin-btn-sm admin-btn-edit">Éditer</a>
<form method="post" action="actions/publish.php" class="publish-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="thesis_id" value="<?= $thesis['id'] ?>">
<?php if ($thesis['is_published']): ?>
<input type="hidden" name="action" value="unpublish">
<button type="submit" class="admin-btn-sm admin-btn-unpublish">Dépublier</button>
<?php else: ?>
<input type="hidden" name="action" value="publish">
<button type="submit" class="admin-btn-sm admin-btn-publish">Publier</button>
<?php endif; ?>
</form>
<form method="post" action="actions/delete.php" id="delete-form-<?= $thesis['id'] ?>" class="publish-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="thesis_id" value="<?= $thesis['id'] ?>">
<button type="button" class="admin-btn-sm admin-btn-delete"
onclick="deleteThesis(<?= $thesis['id'] ?>, <?= htmlspecialchars(json_encode($thesis['title']), ENT_QUOTES) ?>)">Supprimer</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php
$baseParams = array_filter([
'search' => $searchQuery,
'year' => $yearFilter ?: '',
'orientation' => $orientationFilter ?: '',
'ap' => $apFilter ?: '',
'sort' => $sortCol,
'dir' => $sortDir,
]);
include APP_ROOT . '/templates/partials/pagination.php';
?>
</main>
<!-- ══════════════════════════════════════════════════════════════
CONFIRM DIALOGS (replacing browser alert/confirm)
══════════════════════════════════════════════════════════════ -->
<!-- No-selection alert -->
<dialog id="no-selection-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="no-sel-title">
<div class="admin-dialog__header">
<h2 id="no-sel-title">Aucune sélection</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Sélectionnez au moins un TFE avant d'effectuer une action groupée.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn" onclick="this.closest('dialog').close()">OK</button>
</div>
</dialog>
<!-- Bulk publish/unpublish confirm -->
<dialog id="bulk-confirm-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="bulk-confirm-title">
<div class="admin-dialog__header">
<h2 id="bulk-confirm-title">Confirmation</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p><span id="bulk-confirm-word"></span> <span id="bulk-confirm-count"></span> TFE(s) ?</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn" onclick="this.closest('dialog').close(); _executeBulkAction()">Confirmer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- Bulk delete confirm -->
<dialog id="bulk-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="bulk-delete-title">
<div class="admin-dialog__header">
<h2 id="bulk-delete-title">Supprimer des TFE</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer définitivement <strong><span id="bulk-delete-count"></span> TFE(s)</strong> ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--danger" onclick="this.closest('dialog').close(); _executeBulkAction()">Supprimer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- Single thesis delete confirm -->
<dialog id="delete-thesis-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-thesis-title-label">
<div class="admin-dialog__header">
<h2 id="delete-thesis-title-label">Supprimer ce TFE</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer « <strong id="delete-thesis-title"></strong> » ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--danger" onclick="this.closest('dialog').close(); _executeDeleteThesis()">Supprimer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- ══════════════════════════════════════════════════════════════
IMPORT DIALOG
══════════════════════════════════════════════════════════════ -->
<dialog id="import-dialog" class="admin-dialog" aria-labelledby="import-dialog-title">
<div class="admin-dialog__header">
<h2 id="import-dialog-title">Importer une liste de TFE</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="document.getElementById('import-dialog').close()">&#x2715;</button>
</div>
<?php if ($importMessage || !empty($importErrors)): ?>
<div class="admin-import-status-card">
<?php if (!empty($importErrors)): ?>
<div class="toast admin-import-status-card__errors" role="alert" data-type="error">
<strong>⚠ Erreurs :</strong>
<ul class="admin-error-list">
<?php foreach ($importErrors as $err): ?>
<li><?= htmlspecialchars($err) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($importMessage): ?>
<p class="toast admin-import-status-card__success" role="status" data-type="success">✓ <?= htmlspecialchars($importMessage) ?></p>
<?php endif; ?>
</div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data" class="admin-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<div>
<label for="csv_file">Fichier CSV</label>
<div class="admin-file-input">
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
<small class="admin-file-hint">
Colonnes : Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, Année, AP, Orientation, Finalité, Mots-clés, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU<br>
Quatre premières lignes ignorées — Séparateur : virgule — UTF-8
</small>
</div>
</div>
<div class="admin-form-footer">
<button type="submit" class="admin-btn">Importer</button>
<button type="button" class="admin-btn-secondary"
onclick="document.getElementById('import-dialog').close()">Annuler</button>
</div>
</form>
<?php if (!empty($importResults)): ?>
<details class="admin-import-log-details">
<summary>Logs d'importation (<?= count($importResults) ?> entrées)</summary>
<ul class="admin-import-log">
<?php foreach ($importResults as $r): ?>
<li class="admin-import-log__item admin-import-log__item--<?= $r['type'] ?>"><?= htmlspecialchars($r['msg']) ?></li>
<?php endforeach; ?>
</ul>
</details>
<?php endif; ?>
</dialog>
<?php if ($importMessage || !empty($importErrors)): ?>
<script>document.getElementById('import-dialog').showModal();</script>
<?php endif; ?>