mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
- update the structure to have monolithic setup - updated deployments - added live-reloading for devops
452 lines
17 KiB
PHP
452 lines
17 KiB
PHP
<?php
|
|
// List all theses in the database
|
|
session_start();
|
|
|
|
// Generate CSRF token
|
|
if (empty($_SESSION['csrf_token'])) {
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
require_once __DIR__ . '/../lib/Database.php';
|
|
|
|
try {
|
|
$db = new Database();
|
|
$pdo = $db->getPDO();
|
|
|
|
// Get filter parameters
|
|
$searchQuery = isset($_GET['search']) ? trim($_GET['search']) : '';
|
|
$yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null;
|
|
$orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null;
|
|
|
|
// Build query
|
|
$sql = "SELECT
|
|
t.id, t.identifier, t.title, t.subtitle, t.year,
|
|
o.name as orientation,
|
|
ap.name as ap_program,
|
|
GROUP_CONCAT(DISTINCT a.name) as authors,
|
|
t.submitted_at,
|
|
t.is_published
|
|
FROM theses t
|
|
LEFT JOIN orientations o ON t.orientation_id = o.id
|
|
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
|
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
|
LEFT JOIN authors a ON ta.author_id = a.id
|
|
WHERE 1=1";
|
|
|
|
$params = [];
|
|
|
|
if ($searchQuery) {
|
|
$sql .= " AND (t.title LIKE ? OR t.subtitle LIKE ? OR a.name LIKE ?)";
|
|
$searchParam = "%$searchQuery%";
|
|
$params[] = $searchParam;
|
|
$params[] = $searchParam;
|
|
$params[] = $searchParam;
|
|
}
|
|
|
|
if ($yearFilter) {
|
|
$sql .= " AND t.year = ?";
|
|
$params[] = $yearFilter;
|
|
}
|
|
|
|
if ($orientationFilter) {
|
|
$sql .= " AND t.orientation_id = ?";
|
|
$params[] = $orientationFilter;
|
|
}
|
|
|
|
$sql .= " GROUP BY t.id ORDER BY t.year DESC, t.submitted_at DESC";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
$theses = $stmt->fetchAll();
|
|
|
|
// Get unique years for filter
|
|
$yearsStmt = $pdo->query("SELECT DISTINCT year FROM theses ORDER BY year DESC");
|
|
$years = $yearsStmt->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
// Get orientations for filter
|
|
$orientations = $db->getAllOrientations();
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Error loading theses list: " . $e->getMessage());
|
|
die("Erreur lors du chargement de la liste.");
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Liste des TFE - Post-ERG</title>
|
|
<link rel="stylesheet" href="assets/normalize.css">
|
|
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
|
|
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
|
|
<style>
|
|
.filters {
|
|
background: #f5f5f5;
|
|
padding: 1rem;
|
|
margin-bottom: 2rem;
|
|
border-radius: 4px;
|
|
}
|
|
.filters form {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
align-items: end;
|
|
}
|
|
.filters fieldset {
|
|
margin: 0;
|
|
padding: 0;
|
|
border: none;
|
|
min-width: 200px;
|
|
}
|
|
.thesis-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
.thesis-table th,
|
|
.thesis-table td {
|
|
padding: 0.75rem;
|
|
text-align: left;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
.thesis-table th {
|
|
background: #f0f0f0;
|
|
font-weight: bold;
|
|
}
|
|
.thesis-table tr:hover {
|
|
background: #f9f9f9;
|
|
}
|
|
.thesis-title {
|
|
font-weight: bold;
|
|
}
|
|
.thesis-subtitle {
|
|
font-style: italic;
|
|
color: #666;
|
|
font-size: 0.9em;
|
|
}
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 3px;
|
|
font-size: 0.85em;
|
|
}
|
|
.status-pending {
|
|
background: #ffd700;
|
|
color: #000;
|
|
}
|
|
.status-published {
|
|
background: #90ee90;
|
|
color: #000;
|
|
}
|
|
.actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
.btn {
|
|
padding: 0.35rem 0.75rem;
|
|
border-radius: 3px;
|
|
text-decoration: none;
|
|
font-size: 0.9em;
|
|
display: inline-block;
|
|
}
|
|
.btn-view {
|
|
background: #4a90e2;
|
|
color: white;
|
|
}
|
|
.btn-edit {
|
|
background: #f39c12;
|
|
color: white;
|
|
}
|
|
.btn-publish {
|
|
background: #27ae60;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
.btn-unpublish {
|
|
background: #95a5a6;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
.publish-form {
|
|
display: inline;
|
|
margin: 0;
|
|
}
|
|
.stats {
|
|
display: flex;
|
|
gap: 2rem;
|
|
margin-bottom: 2rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
.stat-card {
|
|
background: #f5f5f5;
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
min-width: 150px;
|
|
}
|
|
.stat-number {
|
|
font-size: 2em;
|
|
font-weight: bold;
|
|
color: #4a90e2;
|
|
}
|
|
.stat-label {
|
|
color: #666;
|
|
font-size: 0.9em;
|
|
}
|
|
.bulk-actions {
|
|
background: #f5f5f5;
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
}
|
|
.bulk-actions-buttons {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
.btn-bulk-publish {
|
|
background: #27ae60;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 3px;
|
|
}
|
|
.btn-bulk-unpublish {
|
|
background: #95a5a6;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 3px;
|
|
}
|
|
.select-checkbox {
|
|
cursor: pointer;
|
|
}
|
|
.select-all-checkbox {
|
|
cursor: pointer;
|
|
}
|
|
</style>
|
|
<script>
|
|
function toggleAll(source) {
|
|
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]');
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = source.checked;
|
|
});
|
|
updateBulkActionsVisibility();
|
|
}
|
|
|
|
function updateBulkActionsVisibility() {
|
|
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
|
const bulkActions = document.getElementById('bulk-actions');
|
|
const selectedCount = document.getElementById('selected-count');
|
|
|
|
if (checkboxes.length > 0) {
|
|
bulkActions.style.display = 'flex';
|
|
selectedCount.textContent = checkboxes.length;
|
|
} else {
|
|
bulkActions.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function bulkAction(action) {
|
|
const checkboxes = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
|
if (checkboxes.length === 0) {
|
|
alert('Veuillez sélectionner au moins un TFE.');
|
|
return false;
|
|
}
|
|
|
|
const actionText = action === 'publish' ? 'publier' : 'dépublier';
|
|
if (!confirm(`Voulez-vous vraiment ${actionText} ${checkboxes.length} TFE(s) ?`)) {
|
|
return false;
|
|
}
|
|
|
|
// Set action
|
|
document.getElementById('bulk-action-input').value = action;
|
|
|
|
// Copy selected thesis IDs to hidden form
|
|
const bulkCheckboxesContainer = document.getElementById('bulk-checkboxes');
|
|
bulkCheckboxesContainer.innerHTML = '';
|
|
checkboxes.forEach(checkbox => {
|
|
const input = document.createElement('input');
|
|
input.type = 'hidden';
|
|
input.name = 'selected_theses[]';
|
|
input.value = checkbox.value;
|
|
bulkCheckboxesContainer.appendChild(input);
|
|
});
|
|
|
|
// Submit the form
|
|
document.getElementById('bulk-form').submit();
|
|
return false;
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Add change listeners to all checkboxes
|
|
document.querySelectorAll('input[name="selected_theses[]"]').forEach(checkbox => {
|
|
checkbox.addEventListener('change', updateBulkActionsVisibility);
|
|
});
|
|
});
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Liste des TFE</h1>
|
|
<nav>
|
|
<a href="index.php">← Nouveau TFE</a> |
|
|
<a href="import.php">📥 Importer CSV</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<main>
|
|
<?php if (isset($_SESSION['error'])): ?>
|
|
<div style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
|
|
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (isset($_SESSION['success'])): ?>
|
|
<div style="background: #efe; border: 2px solid #0a0; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #0a0;">
|
|
<strong>✓ <?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?></strong>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div id="bulk-actions" class="bulk-actions" style="display: none;">
|
|
<strong><span id="selected-count">0</span> TFE(s) sélectionné(s)</strong>
|
|
<div class="bulk-actions-buttons">
|
|
<button type="button" class="btn-bulk-publish" onclick="bulkAction('publish')">Publier la sélection</button>
|
|
<button type="button" class="btn-bulk-unpublish" onclick="bulkAction('unpublish')">Dépublier la sélection</button>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="bulk-form" method="post" action="publish.php" style="display: none;">
|
|
<input type="hidden" name="csrf_token" value="<?php echo 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>
|
|
|
|
<div class="stats">
|
|
<div class="stat-card">
|
|
<div class="stat-number"><?php echo count($theses); ?></div>
|
|
<div class="stat-label">TFE total</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => $t['is_published'])); ?></div>
|
|
<div class="stat-label">Publiés</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number"><?php echo count(array_filter($theses, fn($t) => !$t['is_published'])); ?></div>
|
|
<div class="stat-label">En attente</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="filters">
|
|
<form method="get" action="list.php">
|
|
<fieldset>
|
|
<label for="search">Rechercher</label>
|
|
<input type="text" id="search" name="search" placeholder="Titre, auteur..." value="<?php echo htmlspecialchars($searchQuery); ?>">
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<label for="year">Année</label>
|
|
<select id="year" name="year">
|
|
<option value="">Toutes</option>
|
|
<?php foreach ($years as $year): ?>
|
|
<option value="<?php echo $year; ?>" <?php echo $yearFilter == $year ? 'selected' : ''; ?>>
|
|
<?php echo $year; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<label for="orientation">Orientation</label>
|
|
<select id="orientation" name="orientation">
|
|
<option value="">Toutes</option>
|
|
<?php foreach ($orientations as $orientation): ?>
|
|
<option value="<?php echo $orientation['id']; ?>" <?php echo $orientationFilter == $orientation['id'] ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($orientation['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</fieldset>
|
|
|
|
<button type="submit">Filtrer</button>
|
|
<?php if ($searchQuery || $yearFilter || $orientationFilter): ?>
|
|
<a href="list.php">Réinitialiser</a>
|
|
<?php endif; ?>
|
|
</form>
|
|
</div>
|
|
|
|
<?php if (empty($theses)): ?>
|
|
<p>Aucun TFE trouvé.</p>
|
|
<?php else: ?>
|
|
<table class="thesis-table">
|
|
<thead>
|
|
<tr>
|
|
<th><input type="checkbox" class="select-all-checkbox" onchange="toggleAll(this)" title="Tout sélectionner"></th>
|
|
<th>ID</th>
|
|
<th>Titre</th>
|
|
<th>Auteur(s)</th>
|
|
<th>Année</th>
|
|
<th>Orientation</th>
|
|
<th>AP</th>
|
|
<th>Statut</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($theses as $thesis): ?>
|
|
<tr>
|
|
<td><input type="checkbox" class="select-checkbox" name="selected_theses[]" value="<?php echo $thesis['id']; ?>"></td>
|
|
<td><?php echo htmlspecialchars($thesis['identifier'] ?? $thesis['id']); ?></td>
|
|
<td>
|
|
<div class="thesis-title"><?php echo htmlspecialchars($thesis['title']); ?></div>
|
|
<?php if ($thesis['subtitle']): ?>
|
|
<div class="thesis-subtitle"><?php echo htmlspecialchars($thesis['subtitle']); ?></div>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?php echo htmlspecialchars($thesis['authors'] ?? 'N/A'); ?></td>
|
|
<td><?php echo $thesis['year']; ?></td>
|
|
<td><?php echo htmlspecialchars($thesis['orientation'] ?? 'N/A'); ?></td>
|
|
<td><?php echo htmlspecialchars($thesis['ap_program'] ?? 'N/A'); ?></td>
|
|
<td>
|
|
<?php if ($thesis['is_published']): ?>
|
|
<span class="status-badge status-published">Publié</span>
|
|
<?php else: ?>
|
|
<span class="status-badge status-pending">En attente</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<div class="actions">
|
|
<a href="thanks.php?id=<?php echo $thesis['id']; ?>" class="btn btn-view">Voir</a>
|
|
<a href="edit.php?id=<?php echo $thesis['id']; ?>" class="btn btn-edit">Éditer</a>
|
|
<form method="post" action="publish.php" class="publish-form">
|
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
|
<input type="hidden" name="thesis_id" value="<?php echo $thesis['id']; ?>">
|
|
<?php if ($thesis['is_published']): ?>
|
|
<input type="hidden" name="action" value="unpublish">
|
|
<button type="submit" class="btn btn-unpublish" onclick="return confirm('Retirer ce TFE de la publication ?');">Dépublier</button>
|
|
<?php else: ?>
|
|
<input type="hidden" name="action" value="publish">
|
|
<button type="submit" class="btn btn-publish">Publier</button>
|
|
<?php endif; ?>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</main>
|
|
|
|
<footer>
|
|
<p>Post-ERG - <?php echo count($theses); ?> TFE dans la base de données</p>
|
|
</footer>
|
|
</body>
|
|
</html>
|