mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Add SQLite indexes for contenus page language/tag queries + WIP: Peertube orphans, dialogs, contact decoupling, context note, finality types
This commit is contained in:
@@ -41,7 +41,7 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
|
||||
<?php endif; ?>
|
||||
<?php if ($tmpTotalCount > 0): ?>
|
||||
<button type="button" class="btn btn--sm btn--secondary" id="tmp-cleanup-btn"
|
||||
onclick="document.getElementById('tmp-cleanup-dialog').showModal(); fetchTmpStats()">
|
||||
onclick="document.getElementById('tmp-cleanup-dialog').showModal(); htmx.trigger('#tmp-cleanup-stats','loadStats'); htmx.trigger('#peertube-orphans-wrapper','loadPeertube')">
|
||||
Nettoyer (<?= $tmpTotalCount ?>)
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
@@ -97,289 +97,14 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
|
||||
<?php include APP_ROOT . '/templates/admin/index-table.php'; ?>
|
||||
</main>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
CONFIRM DIALOGS (replacing browser alert/confirm)
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<?php include APP_ROOT . '/templates/admin/partials/dialogs/no-selection.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/admin/partials/dialogs/bulk-confirm.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/admin/partials/dialogs/bulk-delete.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/admin/partials/dialogs/delete-thesis.php'; ?>
|
||||
|
||||
<!-- 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()">✕</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="btn btn--primary" onclick="this.closest('dialog').close()">OK</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<?php include APP_ROOT . '/templates/admin/partials/dialogs/import.php'; ?>
|
||||
|
||||
<!-- 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()">✕</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="btn btn--primary" onclick="this.closest('dialog').close(); execBulk()">Confirmer</button>
|
||||
<button type="button" class="btn 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()">✕</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="btn btn--danger" onclick="this.closest('dialog').close(); execBulk()">Supprimer</button>
|
||||
<button type="button" class="btn 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()">✕</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="btn btn--danger" id="delete-dialog-confirm" onclick="this.closest('dialog').close()">Supprimer</button>
|
||||
<button type="button" class="btn 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()">✕</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="admin-import-status-card__success" role="status">✓ <?= htmlspecialchars($importMessage) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($importMessage): ?>
|
||||
<?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; ?>
|
||||
<div class="admin-form-footer">
|
||||
<button type="button" class="btn btn--primary"
|
||||
onclick="document.getElementById('import-dialog').close(); window.location.href = window.location.pathname">Terminé</button>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<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"
|
||||
class="tfe-file-picker"
|
||||
data-queue-type="csv_import"
|
||||
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="btn btn--primary">Importer</button>
|
||||
<button type="button" class="btn 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; ?>
|
||||
<?php endif; ?>
|
||||
</dialog>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
TMP CLEANUP DIALOG
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<dialog id="tmp-cleanup-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="tmp-cleanup-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h2 id="tmp-cleanup-title">Nettoyer les fichiers temporaires</h2>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="document.getElementById('tmp-cleanup-dialog').close()">✕</button>
|
||||
</div>
|
||||
<div class="admin-dialog__body">
|
||||
<p>Les fichiers temporaires s'accumulent lorsque des téléversements sont abandonnés (formulaire fermé avant envoi).</p>
|
||||
<div id="tmp-cleanup-stats" class="admin-dialog__stats">
|
||||
Chargement…
|
||||
</div>
|
||||
<p class="admin-dialog__hint">
|
||||
Seuls les fichiers de plus de 2 heures (FilePond) et 30 jours (corbeille) seront supprimés.
|
||||
Les téléversements récents sont conservés.
|
||||
</p>
|
||||
|
||||
<div id="tmp-cleanup-result" style="display:none"></div>
|
||||
</div>
|
||||
<div class="admin-dialog__footer">
|
||||
<button type="button" class="btn btn--danger" id="tmp-cleanup-confirm"
|
||||
onclick="executeTmpCleanup()">
|
||||
Nettoyer
|
||||
</button>
|
||||
<button type="button" class="btn btn--secondary"
|
||||
onclick="document.getElementById('tmp-cleanup-dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
async function fetchTmpStats() {
|
||||
const el = document.getElementById('tmp-cleanup-stats');
|
||||
try {
|
||||
const resp = await fetch('/admin/actions/cleanup-stats.php');
|
||||
const data = await resp.json();
|
||||
let html = '';
|
||||
const totalStale = data.filepond_stale_count + data.trash_stale_count;
|
||||
|
||||
if (totalStale === 0) {
|
||||
html = '<p style="margin:0;color:var(--accent-green)">✓ Aucun fichier obsolète à nettoyer.</p>';
|
||||
if ((data.filepond_active_count || 0) + (data.trash_active_count || 0) > 0) {
|
||||
html += '<p style="margin:var(--space-xs) 0 0 0;font-size:0.85em;color:var(--text-secondary)">';
|
||||
if (data.filepond_active_count) html += `📁 ${data.filepond_active_count} téléversement(s) actif(s) (session existante) — ${data.filepond_active_human}<br>`;
|
||||
if (data.trash_active_count) html += `🗑️ ${data.trash_active_count} fichier(s) récent(s) en corbeille — ${data.trash_active_human}`;
|
||||
html += '</p>';
|
||||
}
|
||||
} else {
|
||||
html = `<p style="margin:0 0 var(--space-xs) 0;font-weight:600">⚠️ ${totalStale} élément(s) obsolète(s) à nettoyer :</p>`;
|
||||
if (data.filepond_stale_count) {
|
||||
html += `<details style="margin:0 0 var(--space-xs) 0;font-size:0.9em" open><summary>📁 <strong>Téléversements abandonnés</strong> : ${data.filepond_stale_count} dossier(s) — ${data.filepond_stale_human} <span style="font-size:0.85em;color:var(--text-secondary)">(session expirée ou >2h)</span></summary>`;
|
||||
if (data.filepond_stale_files) {
|
||||
html += '<ul style="margin:var(--space-xs) 0 0 var(--space-md);max-height:10em;overflow-y:auto">';
|
||||
data.filepond_stale_files.forEach(f => { html += `<li>${f.name} <span style="color:var(--text-secondary)">(${f.human}, ~${Math.round(f.age_minutes)}min)</span></li>`; });
|
||||
html += '</ul>';
|
||||
}
|
||||
html += '</details>';
|
||||
}
|
||||
if (data.trash_stale_count) {
|
||||
html += `<details style="margin:0 0 var(--space-xs) 0;font-size:0.9em" open><summary>🗑️ <strong>Fichiers supprimés orphelins</strong> : ${data.trash_stale_count} fichier(s) — ${data.trash_stale_human} <span style="font-size:0.85em;color:var(--text-secondary)">(référence DB disparue ou >30j)</span></summary>`;
|
||||
if (data.trash_stale_files) {
|
||||
html += '<ul style="margin:var(--space-xs) 0 0 var(--space-md);max-height:10em;overflow-y:auto">';
|
||||
data.trash_stale_files.forEach(f => { html += `<li>${f.name} <span style="color:var(--text-secondary)">(${f.human}, ~${Math.round(f.age_days)}j)</span></li>`; });
|
||||
html += '</ul>';
|
||||
}
|
||||
html += '</details>';
|
||||
}
|
||||
if (data.filepond_active_count || data.trash_active_count) {
|
||||
html += '<p style="margin:var(--space-xs) 0 0 0;font-size:0.85em;color:var(--text-secondary)">Conservés : ';
|
||||
if (data.filepond_active_count) html += `${data.filepond_active_count} téléversement(s) actif(s), `;
|
||||
if (data.trash_active_count) html += `${data.trash_active_count} fichier(s) récent(s)`;
|
||||
html += '</p>';
|
||||
}
|
||||
}
|
||||
el.innerHTML = html;
|
||||
|
||||
if (totalStale === 0) {
|
||||
document.getElementById('tmp-cleanup-confirm').disabled = true;
|
||||
document.getElementById('tmp-cleanup-confirm').textContent = 'Rien à nettoyer';
|
||||
}
|
||||
} catch (e) {
|
||||
el.textContent = 'Erreur lors du chargement des statistiques.';
|
||||
}
|
||||
}
|
||||
|
||||
async function executeTmpCleanup() {
|
||||
const btn = document.getElementById('tmp-cleanup-confirm');
|
||||
const result = document.getElementById('tmp-cleanup-result');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Nettoyage…';
|
||||
try {
|
||||
const resp = await fetch('/admin/actions/cleanup-tmp.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: 'csrf_token=' + encodeURIComponent('<?= htmlspecialchars($_SESSION['csrf_token']) ?>')
|
||||
});
|
||||
const data = await resp.json();
|
||||
result.style.display = 'block';
|
||||
if (data.success) {
|
||||
const total = data.filepond_removed + data.trash_removed;
|
||||
if (total === 0) {
|
||||
result.className = 'flash-success';
|
||||
result.innerHTML = '✓ Aucun fichier obsolète trouvé.';
|
||||
} else {
|
||||
result.className = 'flash-success';
|
||||
let msg = `✓ ${total} élément(s) supprimé(s) : `;
|
||||
if (data.filepond_removed) msg += `${data.filepond_removed} téléversement(s) abandonné(s), `;
|
||||
if (data.trash_removed) msg += `${data.trash_removed} fichier(s) orphelin(s)`;
|
||||
result.innerHTML = msg;
|
||||
if (data.details && data.details.length > 0) {
|
||||
result.innerHTML += '<details style="margin-top:var(--space-xs);font-size:0.85em"><summary>Détails (' + data.details.length + ')</summary><ul style="margin:var(--space-xs) 0 0 var(--space-md)">' +
|
||||
data.details.map(d => '<li>' + d + '</li>').join('') + '</ul></details>';
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
document.getElementById('tmp-cleanup-dialog').close();
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
result.className = 'flash-error';
|
||||
result.textContent = 'Erreur : ' + (data.error || 'inconnue');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Réessayer';
|
||||
}
|
||||
} catch (e) {
|
||||
result.style.display = 'block';
|
||||
result.className = 'flash-error';
|
||||
result.textContent = 'Erreur réseau.';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Réessayer';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<?php include APP_ROOT . '/templates/admin/partials/dialogs/tmp-cleanup.php'; ?>
|
||||
|
||||
<?php if ($importMessage || !empty($importErrors)): ?>
|
||||
<script>document.getElementById('import-dialog').showModal();</script>
|
||||
|
||||
14
app/templates/admin/partials/dialogs/bulk-confirm.php
Normal file
14
app/templates/admin/partials/dialogs/bulk-confirm.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<dialog id="bulk-confirm-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="bulk-confirm-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h3 id="bulk-confirm-title">Confirmation</h3>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="this.closest('dialog').close()">✕</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="btn btn--primary" onclick="this.closest('dialog').close(); execBulk()">Confirmer</button>
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
14
app/templates/admin/partials/dialogs/bulk-delete.php
Normal file
14
app/templates/admin/partials/dialogs/bulk-delete.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<dialog id="bulk-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="bulk-delete-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h3 id="bulk-delete-title">Supprimer des TFE</h3>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="this.closest('dialog').close()">✕</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="btn btn--danger" onclick="this.closest('dialog').close(); execBulk()">Supprimer</button>
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
14
app/templates/admin/partials/dialogs/delete-thesis.php
Normal file
14
app/templates/admin/partials/dialogs/delete-thesis.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<dialog id="delete-thesis-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-thesis-title-label">
|
||||
<div class="admin-dialog__header">
|
||||
<h3 id="delete-thesis-title-label">Supprimer ce TFE</h3>
|
||||
<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="delete-thesis-title"></strong> » ? Cette action est irréversible.</p>
|
||||
</div>
|
||||
<div class="admin-dialog__footer">
|
||||
<button type="button" class="btn btn--danger" id="delete-dialog-confirm" onclick="this.closest('dialog').close()">Supprimer</button>
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
78
app/templates/admin/partials/dialogs/import.php
Normal file
78
app/templates/admin/partials/dialogs/import.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<dialog id="import-dialog" class="admin-dialog" aria-labelledby="import-dialog-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h3 id="import-dialog-title">Importer une liste de TFE</h3>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="document.getElementById('import-dialog').close()">✕</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="admin-import-status-card__success" role="status">✓ <?= htmlspecialchars($importMessage) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($importMessage): ?>
|
||||
<?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; ?>
|
||||
<div class="admin-form-footer">
|
||||
<button type="button" class="btn btn--primary"
|
||||
onclick="document.getElementById('import-dialog').close(); window.location.href = window.location.pathname">Terminé</button>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<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"
|
||||
class="tfe-file-picker"
|
||||
data-queue-type="csv_import"
|
||||
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="btn btn--primary">Importer</button>
|
||||
<button type="button" class="btn 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; ?>
|
||||
<?php endif; ?>
|
||||
</dialog>
|
||||
13
app/templates/admin/partials/dialogs/no-selection.php
Normal file
13
app/templates/admin/partials/dialogs/no-selection.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<dialog id="no-selection-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="no-sel-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h3 id="no-sel-title">Aucune sélection</h3>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="this.closest('dialog').close()">✕</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="btn btn--primary" onclick="this.closest('dialog').close()">OK</button>
|
||||
</div>
|
||||
</dialog>
|
||||
29
app/templates/admin/partials/dialogs/tmp-cleanup.php
Normal file
29
app/templates/admin/partials/dialogs/tmp-cleanup.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<dialog id="tmp-cleanup-dialog" class="admin-dialog admin-dialog--sheet" aria-labelledby="tmp-cleanup-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h3 id="tmp-cleanup-title">Nettoyer les fichiers temporaires</h3>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="document.getElementById('tmp-cleanup-dialog').close()">✕</button>
|
||||
</div>
|
||||
<div class="admin-dialog__body">
|
||||
<div id="tmp-cleanup-result" style="display:none;margin-bottom:var(--space-sm)"></div>
|
||||
<div class="n-grid" id="cleanup-grid-parent">
|
||||
<!-- ═══════ FilePond / Trash ═══════ -->
|
||||
<details id="tmp-cleanup-stats" class="n-section" open>
|
||||
<summary>Fichiers temporaires</summary>
|
||||
<div hx-get="/admin/actions/cleanup-stats-fragment.php"
|
||||
hx-trigger="loadStats"
|
||||
hx-swap="innerHTML">
|
||||
<p style="margin:0;color:var(--text-secondary)">Chargement…</p>
|
||||
</div>
|
||||
</details>
|
||||
<!-- ═══════ PeerTube ═══════ -->
|
||||
<div id="peertube-orphans-wrapper"
|
||||
hx-get="/admin/actions/peertube-orphans-fragment.php"
|
||||
hx-trigger="loadPeertube"
|
||||
hx-swap="outerHTML"
|
||||
hx-indicator="#peertube-orphans-wrapper">
|
||||
<p style="margin:0;color:var(--text-secondary)">Chargement…</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
@@ -100,7 +100,7 @@ $websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click"
|
||||
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'cover', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
|
||||
📂 Relier un fichier existant
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" style="vertical-align:-2px;margin-right:var(--space-3xs)"><path d="M198.63,57.37a32,32,0,0,0-45.19-.06L141.79,69.52a8,8,0,0,1-11.58-11l11.72-12.29a1.59,1.59,0,0,1,.13-.13,48,48,0,0,1,67.88,67.88,1.59,1.59,0,0,1-.13.13l-12.29,11.72a8,8,0,0,1-11-11.58l12.21-11.65A32,32,0,0,0,198.63,57.37ZM114.21,186.48l-11.65,12.21a32,32,0,0,1-45.25-45.25l12.21-11.65a8,8,0,0,0-11-11.58L46.19,141.93a1.59,1.59,0,0,0-.13.13,48,48,0,0,0,67.88,67.88,1.59,1.59,0,0,0,.13-.13l11.72-12.29a8,8,0,1,0-11.58-11ZM216,152H192a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16ZM40,104H64a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm120,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V192A8,8,0,0,0,160,184ZM96,72a8,8,0,0,0,8-8V40a8,8,0,0,0-16,0V64A8,8,0,0,0,96,72Z"></path></svg> Relier un fichier existant
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -128,7 +128,7 @@ $websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click"
|
||||
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'note_intention', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
|
||||
📂 Relier un fichier existant
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" style="vertical-align:-2px;margin-right:var(--space-3xs)"><path d="M198.63,57.37a32,32,0,0,0-45.19-.06L141.79,69.52a8,8,0,0,1-11.58-11l11.72-12.29a1.59,1.59,0,0,1,.13-.13,48,48,0,0,1,67.88,67.88,1.59,1.59,0,0,1-.13.13l-12.29,11.72a8,8,0,0,1-11-11.58l12.21-11.65A32,32,0,0,0,198.63,57.37ZM114.21,186.48l-11.65,12.21a32,32,0,0,1-45.25-45.25l12.21-11.65a8,8,0,0,0-11-11.58L46.19,141.93a1.59,1.59,0,0,0-.13.13,48,48,0,0,0,67.88,67.88,1.59,1.59,0,0,0,.13-.13l11.72-12.29a8,8,0,1,0-11.58-11ZM216,152H192a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16ZM40,104H64a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm120,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V192A8,8,0,0,0,160,184ZM96,72a8,8,0,0,0,8-8V40a8,8,0,0,0-16,0V64A8,8,0,0,0,96,72Z"></path></svg> Relier un fichier existant
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -166,8 +166,20 @@ $websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click"
|
||||
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'tfe', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
|
||||
📂 Relier un fichier existant
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" style="vertical-align:-2px;margin-right:var(--space-3xs)"><path d="M198.63,57.37a32,32,0,0,0-45.19-.06L141.79,69.52a8,8,0,0,1-11.58-11l11.72-12.29a1.59,1.59,0,0,1,.13-.13,48,48,0,0,1,67.88,67.88,1.59,1.59,0,0,1-.13.13l-12.29,11.72a8,8,0,0,1-11-11.58l12.21-11.65A32,32,0,0,0,198.63,57.37ZM114.21,186.48l-11.65,12.21a32,32,0,0,1-45.25-45.25l12.21-11.65a8,8,0,0,0-11-11.58L46.19,141.93a1.59,1.59,0,0,0-.13.13,48,48,0,0,0,67.88,67.88,1.59,1.59,0,0,0,.13-.13l11.72-12.29a8,8,0,1,0-11.58-11ZM216,152H192a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16ZM40,104H64a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm120,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V192A8,8,0,0,0,160,184ZM96,72a8,8,0,0,0,8-8V40a8,8,0,0,0-16,0V64A8,8,0,0,0,96,72Z"></path></svg> Relier un fichier existant
|
||||
</button>
|
||||
<?php if ($peerTubeEnabled): ?>
|
||||
<button type="button" class="btn btn--sm btn--ghost peertube-browser-trigger"
|
||||
data-thesis-id="<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>"
|
||||
hx-get="/admin/fragments/peertube-browser.php"
|
||||
hx-target="#peertube-relink-modal-body"
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click"
|
||||
onclick="document.getElementById('peertube-relink-modal').showModal(); window.__xamxamPeertubeRelinkCtx = { thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" style="vertical-align:-2px;margin-right:var(--space-3xs)"><path d="M198.63,57.37a32,32,0,0,0-45.19-.06L141.79,69.52a8,8,0,0,1-11.58-11l11.72-12.29a1.59,1.59,0,0,1,.13-.13,48,48,0,0,1,67.88,67.88,1.59,1.59,0,0,1-.13.13l-12.29,11.72a8,8,0,0,1-11-11.58l12.21-11.65A32,32,0,0,0,198.63,57.37ZM114.21,186.48l-11.65,12.21a32,32,0,0,1-45.25-45.25l12.21-11.65a8,8,0,0,0-11-11.58L46.19,141.93a1.59,1.59,0,0,0-.13.13,48,48,0,0,0,67.88,67.88,1.59,1.59,0,0,0,.13-.13l11.72-12.29a8,8,0,1,0-11.58-11ZM216,152H192a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16ZM40,104H64a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm120,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V192A8,8,0,0,0,160,184ZM96,72a8,8,0,0,0,8-8V40a8,8,0,0,0-16,0V64A8,8,0,0,0,96,72Z"></path></svg> Relier une vidéo PeerTube
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -195,7 +207,7 @@ $websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click"
|
||||
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'annexe', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
|
||||
📂 Relier un fichier existant
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" style="vertical-align:-2px;margin-right:var(--space-3xs)"><path d="M198.63,57.37a32,32,0,0,0-45.19-.06L141.79,69.52a8,8,0,0,1-11.58-11l11.72-12.29a1.59,1.59,0,0,1,.13-.13,48,48,0,0,1,67.88,67.88,1.59,1.59,0,0,1-.13.13l-12.29,11.72a8,8,0,0,1-11-11.58l12.21-11.65A32,32,0,0,0,198.63,57.37ZM114.21,186.48l-11.65,12.21a32,32,0,0,1-45.25-45.25l12.21-11.65a8,8,0,0,0-11-11.58L46.19,141.93a1.59,1.59,0,0,0-.13.13,48,48,0,0,0,67.88,67.88,1.59,1.59,0,0,0,.13-.13l11.72-12.29a8,8,0,1,0-11.58-11ZM216,152H192a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16ZM40,104H64a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm120,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V192A8,8,0,0,0,160,184ZM96,72a8,8,0,0,0,8-8V40a8,8,0,0,0-16,0V64A8,8,0,0,0,96,72Z"></path></svg> Relier un fichier existant
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -219,4 +231,22 @@ $websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
|
||||
<!-- ═══════════════════ File Browser Modal (edit mode only) ═══════════════════ -->
|
||||
<?php if ($editMode): ?>
|
||||
<?php include APP_ROOT . '/templates/partials/form/file-browser-fragment.php'; ?>
|
||||
|
||||
<!-- PeerTube relink modal (edit mode, PeerTube enabled) -->
|
||||
<?php if ($peerTubeEnabled): ?>
|
||||
<dialog id="peertube-relink-modal" class="relink-modal">
|
||||
<div class="relink-modal-header">
|
||||
<h3>Relier une vidéo PeerTube</h3>
|
||||
<button type="button" class="btn btn--sm btn--ghost"
|
||||
onclick="document.getElementById('peertube-relink-modal').close()"
|
||||
aria-label="Fermer">✕</button>
|
||||
</div>
|
||||
<div id="peertube-relink-modal-body">
|
||||
<p class="file-browser-loading">Chargement des vidéos orphelines…</p>
|
||||
</div>
|
||||
<div class="relink-modal-footer">
|
||||
<small>Seules les vidéos présentes sur la chaîne mais non liées à un TFE sont listées.</small>
|
||||
</div>
|
||||
</dialog>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
* bool $showContact — Contact checkbox fieldset
|
||||
* bool $showCoverPreview — cover image preview + remove checkbox
|
||||
* bool $showExistingFiles — existing thesis files list (deletable)
|
||||
* bool $showBackoffice — Backoffice fieldset (context_note, jury_points, remarks, baiu_link, exemplaires, contact_visible, contact_interne, is_published)
|
||||
* bool $showBackoffice — Backoffice fieldset (context_note, jury_points, remarks, baiu_link, exemplaires, contact_interne, is_published)
|
||||
* bool $showEmailConfirmation — E-mail de confirmation fieldset
|
||||
|
||||
* string $helpFn — fn(string $key): string (for help blocks)
|
||||
@@ -45,7 +45,7 @@
|
||||
* array $currentFiles — existing thesis files for edit mode
|
||||
* ?string $currentContextNote — existing context note for edit mode
|
||||
* array $currentRaw — raw thesis row for edit mode
|
||||
* ?string $contactPublic — contact visibility flag for edit mode
|
||||
* ?bool $contactPublic — contact visibility flag for edit mode
|
||||
* ?string $contactInterne — contact email for edit mode
|
||||
*
|
||||
* Autosave:
|
||||
@@ -455,7 +455,7 @@ if ($filesMode === 'add'): ?>
|
||||
</div>
|
||||
<!-- Integer input for pages / Mo -->
|
||||
<div id="duration-value-integer"<?= $_durUnit === 'durée' ? ' style="display:none"' : '' ?>>
|
||||
<label for="duration_value_int">Valeur :</label>
|
||||
<label for="duration_value_int" id="duration-value-label">Valeur :</label>
|
||||
<input type="number" id="duration_value_int"
|
||||
value="<?= htmlspecialchars($_durUnit !== 'durée' ? (string)($_durFloat ?? '') : '') ?>"
|
||||
step="1" min="0" placeholder="0"
|
||||
@@ -648,10 +648,13 @@ if ($filesMode === 'add'): ?>
|
||||
var hidden = document.getElementById('duration_value');
|
||||
var intWrap = document.getElementById('duration-value-integer');
|
||||
var intInput = document.getElementById('duration_value_int');
|
||||
var intLabel = document.getElementById('duration-value-label');
|
||||
var timeWrap = document.getElementById('duration-value-time');
|
||||
var hInput = document.getElementById('duration_h');
|
||||
var mInput = document.getElementById('duration_m');
|
||||
var sInput = document.getElementById('duration_s');
|
||||
|
||||
var LABELS = { pages: 'Nombre :', mo: 'Taille :', 'durée': 'Durée :' };
|
||||
if (!unit || !hidden) return;
|
||||
|
||||
function updateHidden() {
|
||||
@@ -673,6 +676,7 @@ if ($filesMode === 'add'): ?>
|
||||
} else {
|
||||
timeWrap.style.display = 'none';
|
||||
intWrap.style.display = '';
|
||||
if (intLabel) intLabel.textContent = LABELS[unit.value] || 'Valeur :';
|
||||
}
|
||||
updateHidden();
|
||||
}
|
||||
|
||||
@@ -207,14 +207,8 @@
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data["context_note"])): ?>
|
||||
<p class="tfe-meta-item tfe-meta-note">
|
||||
<span class="tfe-meta-label">Note contextuelle relative à soutenance :</span>
|
||||
<span class="tfe-note-value"><?= nl2br(htmlspecialchars($data["context_note"])) ?></span>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data["contact_visible"])): ?>
|
||||
<?php if (!empty($data["contact_visible"]) && !empty($data["contact_public"])): ?>
|
||||
<p class="tfe-meta-item">
|
||||
<span class="tfe-meta-label">Contact :</span>
|
||||
<?php
|
||||
@@ -271,13 +265,19 @@
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($data["synopsis"])): ?>
|
||||
<div class="tfe-synopsis-text">
|
||||
<?= nl2br(htmlspecialchars($data["synopsis"])) ?>
|
||||
<div class="tfe-synopsis-column">
|
||||
<?php if (!empty($data["context_note"])): ?>
|
||||
<p class="tfe-context-note"><em>Note Contextuelle :</em><br><?= nl2br(htmlspecialchars($data["context_note"])) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data["synopsis"])): ?>
|
||||
<div class="tfe-synopsis-text">
|
||||
<?= nl2br(htmlspecialchars($data["synopsis"])) ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="tfe-synopsis-text tfe-synopsis-empty"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="tfe-synopsis-text tfe-synopsis-empty"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- ROW 3: All files — flex container -->
|
||||
|
||||
Reference in New Issue
Block a user