mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
Redesign UI to match target design images
- Flat purple-gradient nav bar with POSTERG/RÉPERTOIRE/À PROPOS links - Full-width search bar with icon, bottom-border only, below nav - Home: white bg, media card grid (thumbnail + author/title label below) - Répertoire: 4-column index (Années/Catégories/Étudiantes/Mots-clés) - TFE: 2-column layout (large text left, media right) - À Propos: 2-column, large monospace text, new apropos.php page - Admin: dark theme (#1a1a1a), purple gradient nav, bottom-border inputs - New shared partials: templates/nav.php, templates/search-bar.php - Rewrote all CSS: common, main, search, tfe, apropos, admin
This commit is contained in:
@@ -1,263 +1,238 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
if (empty($_SESSION["csrf_token"])) {
|
||||
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
$pageTitle = "Ajout de TFE";
|
||||
$pageTitle = "Ajouter un TFE";
|
||||
|
||||
// Load database helper
|
||||
require_once __DIR__ . '/../../src/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
$orientations = $db->getAllOrientations();
|
||||
$apPrograms = $db->getAllAPPrograms();
|
||||
$apPrograms = $db->getAllAPPrograms();
|
||||
$finalityTypes = $db->getAllFinalityTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
$languages = $db->getAllLanguages();
|
||||
$formatTypes = $db->getAllFormatTypes();
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to load form data: " . $e->getMessage());
|
||||
die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard.");
|
||||
die("Erreur lors du chargement du formulaire.");
|
||||
}
|
||||
|
||||
// Get error message and preserved form data from session (if redirected back from error)
|
||||
$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null;
|
||||
$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : [];
|
||||
$error = $_SESSION["form_error"] ?? null;
|
||||
$formData = $_SESSION["form_data"] ?? [];
|
||||
unset($_SESSION["form_error"], $_SESSION["form_data"]);
|
||||
|
||||
// Clear session data after retrieving
|
||||
unset($_SESSION["form_error"]);
|
||||
unset($_SESSION["form_data"]);
|
||||
|
||||
// Helper function to get old form value
|
||||
function old($key, $default = "")
|
||||
{
|
||||
function old($key, $default = "") {
|
||||
global $formData;
|
||||
return isset($formData[$key])
|
||||
? htmlspecialchars($formData[$key])
|
||||
: $default;
|
||||
return isset($formData[$key]) ? htmlspecialchars($formData[$key]) : $default;
|
||||
}
|
||||
|
||||
// Helper function to check if value was previously selected
|
||||
function wasSelected($key, $value)
|
||||
{
|
||||
function wasSelected($key, $value) {
|
||||
global $formData;
|
||||
if (!isset($formData[$key])) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($formData[$key])) {
|
||||
return in_array($value, $formData[$key]);
|
||||
}
|
||||
if (!isset($formData[$key])) return false;
|
||||
if (is_array($formData[$key])) return in_array($value, $formData[$key]);
|
||||
return $formData[$key] == $value;
|
||||
}
|
||||
?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
||||
<main>
|
||||
|
||||
<main class="admin-main">
|
||||
<h1 class="admin-page-title">Ajouter un TFE</h1>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error-message">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<div class="admin-alert admin-alert--error">⚠ <?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="actions/formulaire.php" method="post" enctype="multipart/form-data">
|
||||
<!-- CSRF Protection -->
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(
|
||||
$_SESSION["csrf_token"],
|
||||
); ?>">
|
||||
<form action="actions/formulaire.php" method="post" enctype="multipart/form-data" class="admin-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||
|
||||
<!-- Titre -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="titre">Titre :</label>
|
||||
<input class="admin-input" type="text" id="titre" name="titre"
|
||||
value="<?= old('titre') ?>" required>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Informations de base</legend>
|
||||
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
||||
<input type="text" id="auteurice" name="auteurice" placeholder="Nom de l'auteur·ice" value="<?php echo old(
|
||||
"auteurice",
|
||||
); ?>" required>
|
||||
<br>
|
||||
<label for="mail">Contact (email, site web, insta, ...)</label>
|
||||
<input type="text" id="mail" name="mail" placeholder="votre.email@example.com ou @instagram" value="<?php echo old(
|
||||
"mail",
|
||||
); ?>">
|
||||
<br>
|
||||
<label for="année">Année diplômante *</label>
|
||||
<input type="number" id="année" name="année" min="2000" max="<?php echo date(
|
||||
"Y",
|
||||
) + 1; ?>" placeholder="<?php echo date(
|
||||
"Y",
|
||||
); ?>" value="<?php echo old("année"); ?>" required>
|
||||
</fieldset>
|
||||
<!-- Sous-titre -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="subtitle">Sous-titre (si applicable) :</label>
|
||||
<input class="admin-input" type="text" id="subtitle" name="subtitle"
|
||||
value="<?= old('subtitle') ?>">
|
||||
</div>
|
||||
|
||||
<!-- Auteur·ice -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="auteurice">Auteur·ice(s) :</label>
|
||||
<input class="admin-input" type="text" id="auteurice" name="auteurice"
|
||||
value="<?= old('auteurice') ?>" required>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Informations académiques</legend>
|
||||
<label for="orientation">Orientation principale *</label>
|
||||
<select id="orientation" name="orientation" required>
|
||||
<option value="">-- Sélectionner une orientation --</option>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$orientation["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"orientation",
|
||||
$orientation["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars(
|
||||
$orientation["name"],
|
||||
); ?>
|
||||
<!-- Contact -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="mail">Contact(s) (optionnel) [mail/site/insta/etc.] :</label>
|
||||
<input class="admin-input" type="text" id="mail" name="mail"
|
||||
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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Année -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="année">Année :</label>
|
||||
<input class="admin-input" type="number" id="année" name="année"
|
||||
min="2000" max="<?= date('Y') + 1 ?>"
|
||||
placeholder="<?= date('Y') ?>"
|
||||
value="<?= old('année') ?>" required>
|
||||
</div>
|
||||
|
||||
<!-- Orientation -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="orientation">Orientation :</label>
|
||||
<select class="admin-select" id="orientation" name="orientation" required>
|
||||
<option value=""></option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= htmlspecialchars($o['id']) ?>"
|
||||
<?= wasSelected('orientation', $o['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<label for="ap">Atelier Pratique (AP) *</label>
|
||||
<select id="ap" name="ap" required>
|
||||
<option value="">-- Sélectionner un AP --</option>
|
||||
<!-- AP -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="ap">Atelier pluridisciplinaire :</label>
|
||||
<select class="admin-select" id="ap" name="ap" required>
|
||||
<option value=""></option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$ap["id"],
|
||||
); ?>" <?php echo wasSelected("ap", $ap["id"])
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($ap["name"]); ?>
|
||||
<?php if (
|
||||
$ap["code"]
|
||||
): ?> (<?php echo htmlspecialchars(
|
||||
$ap["code"],
|
||||
); ?>)<?php endif; ?>
|
||||
<option value="<?= htmlspecialchars($ap['id']) ?>"
|
||||
<?= wasSelected('ap', $ap['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($ap['name']) ?><?php if ($ap['code']): ?> (<?= htmlspecialchars($ap['code']) ?>)<?php endif; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<label for="finality">Finalité du master *</label>
|
||||
<select id="finality" name="finality" required>
|
||||
<option value="">-- Sélectionner une finalité --</option>
|
||||
<?php foreach ($finalityTypes as $finality): ?>
|
||||
<option value="<?php echo htmlspecialchars(
|
||||
$finality["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"finality",
|
||||
$finality["id"],
|
||||
)
|
||||
? "selected"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($finality["name"]); ?>
|
||||
<!-- Finalité -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="finality">Finalité du master :</label>
|
||||
<select class="admin-select" id="finality" name="finality" required>
|
||||
<option value=""></option>
|
||||
<?php foreach ($finalityTypes as $f): ?>
|
||||
<option value="<?= htmlspecialchars($f['id']) ?>"
|
||||
<?= wasSelected('finality', $f['id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($f['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<label for="promoteurice">Promoteur·ice(s)</label>
|
||||
<input type="text" id="promoteurice" name="promoteurice" placeholder="Nom du/de la promoteur·ice (si plusieurs, séparer par des virgules)" value="<?php echo old(
|
||||
"promoteurice",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend>À propos du TFE</legend>
|
||||
<label for="titre">Titre du mémoire *</label>
|
||||
<input type="text" id="titre" name="titre" placeholder="Titre de votre TFE" value="<?php echo old(
|
||||
"titre",
|
||||
); ?>" required>
|
||||
|
||||
<br>
|
||||
<label for="subtitle">Sous-titre (si applicable)</label>
|
||||
<input type="text" id="subtitle" name="subtitle" placeholder="Sous-titre de votre TFE" value="<?php echo old(
|
||||
"subtitle",
|
||||
); ?>">
|
||||
<br>
|
||||
<label for="synopsis">Synopsis (environ 200 mots) *</label>
|
||||
<textarea id="synopsis" name="synopsis" rows="8" placeholder="Décrivez votre TFE en quelques paragraphes..." required><?php echo old(
|
||||
"synopsis",
|
||||
); ?></textarea>
|
||||
<br>
|
||||
<label for="problématique">Problématique</label>
|
||||
<textarea id="problématique" name="problématique" rows="4" placeholder="La problématique principale de votre mémoire..."><?php echo old(
|
||||
"problématique",
|
||||
); ?></textarea>
|
||||
<br>
|
||||
<label>Langue(s) du TFE * (sélection multiple possible)</label>
|
||||
<ul class="no-style">
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<li>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="languages[]" value="<?php echo htmlspecialchars(
|
||||
$language["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"languages",
|
||||
$language["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($language["name"]); ?>
|
||||
</label>
|
||||
</li>
|
||||
<!-- Langue(s) -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label">Langue(s) :</label>
|
||||
<div class="admin-checkbox-list">
|
||||
<?php foreach ($languages as $lang): ?>
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="languages[]"
|
||||
value="<?= htmlspecialchars($lang['id']) ?>"
|
||||
<?= wasSelected('languages', $lang['id']) ? 'checked' : '' ?>>
|
||||
<?= htmlspecialchars($lang['name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<label>Format(s) (sélection multiple possible)</label>
|
||||
<ul class="no-style">
|
||||
<?php foreach ($formatTypes as $format): ?>
|
||||
<li>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="formats[]" value="<?php echo htmlspecialchars(
|
||||
$format["id"],
|
||||
); ?>" <?php echo wasSelected(
|
||||
"formats",
|
||||
$format["id"],
|
||||
)
|
||||
? "checked"
|
||||
: ""; ?>>
|
||||
<?php echo htmlspecialchars($format["name"]); ?>
|
||||
</label>
|
||||
</li>
|
||||
<!-- Format(s) -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label">Format(s) :</label>
|
||||
<div class="admin-checkbox-list">
|
||||
<?php foreach ($formatTypes as $fmt): ?>
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="formats[]"
|
||||
value="<?= htmlspecialchars($fmt['id']) ?>"
|
||||
<?= wasSelected('formats', $fmt['id']) ? 'checked' : '' ?>>
|
||||
<?= htmlspecialchars($fmt['name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<label for="tag">Mots-clés (max 10, séparés par des virgules)</label>
|
||||
<input type="text" id="tag" name="tag" placeholder="typographie, photographie, outils libre, post-colonial..." value="<?php echo old(
|
||||
"tag",
|
||||
); ?>">
|
||||
<br>
|
||||
<small>Séparez les mots-clés par des virgules. Maximum 10 mots-clés.</small>
|
||||
<label for="duration_info">Durée/Taille (si applicable)</label>
|
||||
<input type="text" id="duration_info" name="duration_info" placeholder="Ex: 68 minutes, 128 pages, 78 pages + 15 minutes" value="<?php echo old(
|
||||
"duration_info",
|
||||
); ?>">
|
||||
<br>
|
||||
<small>Indiquez la durée (en minutes) ou le nombre de pages de votre TFE.</small>
|
||||
<label for="lien">Lien vers un site web ou ressource en ligne</label>
|
||||
<input type="url" id="lien" name="lien" placeholder="https://monmemoire.erg.be/..." value="<?php echo old(
|
||||
"lien",
|
||||
); ?>">
|
||||
</fieldset>
|
||||
<!-- Mots-clés -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="tag">Mots-clés :</label>
|
||||
<div>
|
||||
<input class="admin-input" type="text" id="tag" name="tag"
|
||||
placeholder="sociologie, anthropologie, ..."
|
||||
value="<?= old('tag') ?>">
|
||||
<p class="admin-hint">Séparez par des virgules. Max 10 mots-clés.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Synopsis -->
|
||||
<div class="admin-form-row" style="align-items:start;">
|
||||
<label class="admin-label" for="synopsis">Synopsis :</label>
|
||||
<textarea class="admin-textarea" id="synopsis" name="synopsis"
|
||||
rows="7" required><?= old('synopsis') ?></textarea>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Fichiers</legend>
|
||||
<label for="couverture">Importer une image de couverture</label>
|
||||
<small>Formats acceptés : JPG, PNG. Taille max : 10MB.</small>
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
||||
<!-- Durée/Taille -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="duration_info">Durée / Taille :</label>
|
||||
<div>
|
||||
<input class="admin-input" type="text" id="duration_info" name="duration_info"
|
||||
placeholder="Ex : 84 pages"
|
||||
value="<?= old('duration_info') ?>">
|
||||
<p class="admin-hint">Durée (minutes) ou nombre de pages.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="files">Importer le TFE et les fichiers annexes</label>
|
||||
<small>Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB.</small>
|
||||
<small>Si vous voulez importer un dossier, créez une archive ZIP.</small>
|
||||
<input type="file" id="files" name="files[]" multiple accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
||||
</fieldset>
|
||||
<!-- Lien -->
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="lien">Lien (site / ressource) :</label>
|
||||
<input class="admin-input" type="url" id="lien" name="lien"
|
||||
placeholder="https://..."
|
||||
value="<?= old('lien') ?>">
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<input type="submit" name="go" value="Soumettre mon TFE">
|
||||
<!-- Image couverture -->
|
||||
<div class="admin-form-row" style="align-items:start;">
|
||||
<label class="admin-label">Image de couverture :</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
||||
<p class="admin-hint">JPG, PNG. Taille max : 10 MB.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fichiers -->
|
||||
<div class="admin-form-row" style="align-items:start;">
|
||||
<label class="admin-label">Fichiers du TFE :</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="files" name="files[]" multiple
|
||||
accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
||||
<p class="admin-hint">PDF, JPG, PNG, MP4, ZIP. Max 50 MB par fichier.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-submit-wrap">
|
||||
<button type="submit" name="go" class="admin-btn">Soumettre</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
|
||||
@@ -175,147 +175,156 @@ try {
|
||||
?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
||||
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert-error">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<main class="admin-main">
|
||||
<h1 class="admin-page-title">Modifier un TFE</h1>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert-success">
|
||||
<strong>✓ <?php echo htmlspecialchars($success); ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="admin-alert admin-alert--error">⚠ <?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success): ?>
|
||||
<div class="admin-alert admin-alert--success">✓ <?= htmlspecialchars($success) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="edit.php?id=<?php echo $thesisId; ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<form method="post" action="edit.php?id=<?= $thesisId ?>" class="admin-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
|
||||
<h2>Informations de base</h2>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="auteurice">Auteur·ice(s) :</label>
|
||||
<input class="admin-input" type="text" id="auteurice" name="auteurice"
|
||||
value="<?= htmlspecialchars($thesis['authors']) ?>" required>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
||||
<input type="text" id="auteurice" name="auteurice" value="<?php echo htmlspecialchars($thesis['authors']); ?>" required>
|
||||
<small>Si plusieurs, séparer par des virgules</small>
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="mail">Contact :</label>
|
||||
<input class="admin-input" type="text" id="mail" name="mail" value="">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="mail">Contact</label>
|
||||
<input type="text" id="mail" name="mail" value="">
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="année">Année :</label>
|
||||
<input class="admin-input" type="number" id="année" name="année"
|
||||
value="<?= $thesis['year'] ?>" required>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="année">Année *</label>
|
||||
<input type="number" id="année" name="année" value="<?php echo $thesis['year']; ?>" required>
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="orientation">Orientation :</label>
|
||||
<select class="admin-select" id="orientation" name="orientation" required>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= $o['id'] ?>"
|
||||
<?= ($thesis['orientation'] == $o['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2>Informations académiques</h2>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="ap">Atelier pluridisciplinaire :</label>
|
||||
<select class="admin-select" id="ap" name="ap" required>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?= $ap['id'] ?>"
|
||||
<?= ($thesis['ap_program'] == $ap['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($ap['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="orientation">Orientation *</label>
|
||||
<select id="orientation" name="orientation" required>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?php echo $orientation['id']; ?>" <?php echo ($thesis['orientation'] == $orientation['name']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($orientation['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="finality">Finalité du master :</label>
|
||||
<select class="admin-select" id="finality" name="finality" required>
|
||||
<?php foreach ($finalityTypes as $f): ?>
|
||||
<option value="<?= $f['id'] ?>"
|
||||
<?= ($thesis['finality_type'] == $f['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($f['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="ap">Atelier Pratique *</label>
|
||||
<select id="ap" name="ap" required>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?php echo $ap['id']; ?>" <?php echo ($thesis['ap_program'] == $ap['name']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($ap['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="promoteurice">Promoteur·ice(s) :</label>
|
||||
<input class="admin-input" type="text" id="promoteurice" name="promoteurice"
|
||||
value="<?= htmlspecialchars($thesis['supervisors'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="finality">Finalité *</label>
|
||||
<select id="finality" name="finality" required>
|
||||
<?php foreach ($finalityTypes as $finality): ?>
|
||||
<option value="<?php echo $finality['id']; ?>" <?php echo ($thesis['finality_type'] == $finality['name']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($finality['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="titre">Titre :</label>
|
||||
<input class="admin-input" type="text" id="titre" name="titre"
|
||||
value="<?= htmlspecialchars($thesis['title']) ?>" required>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="promoteurice">Promoteur·ice(s)</label>
|
||||
<input type="text" id="promoteurice" name="promoteurice" value="<?php echo htmlspecialchars($thesis['supervisors'] ?? ''); ?>">
|
||||
<small>Si plusieurs, séparer par des virgules</small>
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="subtitle">Sous-titre :</label>
|
||||
<input class="admin-input" type="text" id="subtitle" name="subtitle"
|
||||
value="<?= htmlspecialchars($thesis['subtitle'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<h2>À propos du TFE</h2>
|
||||
<div class="admin-form-row" style="align-items:start;">
|
||||
<label class="admin-label" for="synopsis">Synopsis :</label>
|
||||
<textarea class="admin-textarea" id="synopsis" name="synopsis" rows="7" required><?= htmlspecialchars($thesis['synopsis'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="titre">Titre *</label>
|
||||
<input type="text" id="titre" name="titre" value="<?php echo htmlspecialchars($thesis['title']); ?>" required>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="subtitle">Sous-titre</label>
|
||||
<input type="text" id="subtitle" name="subtitle" value="<?php echo htmlspecialchars($thesis['subtitle'] ?? ''); ?>">
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="synopsis">Synopsis *</label>
|
||||
<textarea id="synopsis" name="synopsis" rows="8" required><?php echo htmlspecialchars($thesis['synopsis'] ?? ''); ?></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Langue(s) *</label>
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="languages[]" value="<?php echo $language['id']; ?>" <?php echo in_array($language['id'], $currentLanguages) ? 'checked' : ''; ?>>
|
||||
<?php echo htmlspecialchars($language['name']); ?>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label">Langue(s) :</label>
|
||||
<div class="admin-checkbox-list">
|
||||
<?php foreach ($languages as $lang): ?>
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="languages[]" value="<?= $lang['id'] ?>"
|
||||
<?= in_array($lang['id'], $currentLanguages) ? 'checked' : '' ?>>
|
||||
<?= htmlspecialchars($lang['name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label>Format(s)</label>
|
||||
<?php foreach ($formatTypes as $format): ?>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="formats[]" value="<?php echo $format['id']; ?>" <?php echo in_array($format['id'], $currentFormats) ? 'checked' : ''; ?>>
|
||||
<?php echo htmlspecialchars($format['name']); ?>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label">Format(s) :</label>
|
||||
<div class="admin-checkbox-list">
|
||||
<?php foreach ($formatTypes as $fmt): ?>
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="formats[]" value="<?= $fmt['id'] ?>"
|
||||
<?= in_array($fmt['id'], $currentFormats) ? 'checked' : '' ?>>
|
||||
<?= htmlspecialchars($fmt['name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="tag">Mots-clés (max 10)</label>
|
||||
<input type="text" id="tag" name="tag" value="<?php echo htmlspecialchars($thesis['keywords'] ?? ''); ?>">
|
||||
<small>Séparer par des virgules</small>
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="tag">Mots-clés :</label>
|
||||
<div>
|
||||
<input class="admin-input" type="text" id="tag" name="tag"
|
||||
value="<?= htmlspecialchars($thesis['keywords'] ?? '') ?>">
|
||||
<p class="admin-hint">Séparer par des virgules. Max 10.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="duration_info">Durée/Taille</label>
|
||||
<input type="text" id="duration_info" name="duration_info" value="<?php echo htmlspecialchars($thesis['file_size_info'] ?? ''); ?>">
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="duration_info">Durée / Taille :</label>
|
||||
<input class="admin-input" type="text" id="duration_info" name="duration_info"
|
||||
value="<?= htmlspecialchars($thesis['file_size_info'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label for="lien">Lien externe</label>
|
||||
<input type="url" id="lien" name="lien" value="<?php echo htmlspecialchars($thesis['baiu_link'] ?? ''); ?>">
|
||||
</fieldset>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label" for="lien">Lien externe :</label>
|
||||
<input class="admin-input" type="url" id="lien" name="lien"
|
||||
value="<?= htmlspecialchars($thesis['baiu_link'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<h2>Publication</h2>
|
||||
<div class="admin-form-row">
|
||||
<label class="admin-label">Publication :</label>
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="is_published" value="1"
|
||||
<?= $thesis['is_published'] ? 'checked' : '' ?>>
|
||||
Publier ce TFE sur le site public
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="is_published" value="1" <?php echo $thesis['is_published'] ? 'checked' : ''; ?>>
|
||||
<span>Publier ce TFE sur le site public</span>
|
||||
</label>
|
||||
<small>Si coché, ce TFE sera visible sur le site public. Sinon, il restera en attente.</small>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Enregistrer les modifications</button>
|
||||
<a href="/admin/thanks.php?id=<?php echo $thesisId; ?>">Annuler</a>
|
||||
</form>
|
||||
</main>
|
||||
<div class="admin-submit-wrap">
|
||||
<button type="submit" class="admin-btn">Enregistrer</button>
|
||||
<a href="/admin/thanks.php?id=<?= $thesisId ?>" class="admin-btn-secondary" style="margin-left:.75rem;">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
|
||||
@@ -279,72 +279,51 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
||||
|
||||
<main>
|
||||
<h2>Importer des TFE depuis un fichier CSV</h2>
|
||||
<main class="admin-main">
|
||||
<h1 class="admin-page-title">Importer une liste de TFE</h1>
|
||||
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert-error">
|
||||
<strong>⚠️ Erreurs:</strong>
|
||||
<ul>
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?php echo htmlspecialchars($error); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="admin-alert admin-alert--error">
|
||||
<strong>⚠ Erreurs :</strong>
|
||||
<ul style="margin:.5rem 0 0;padding-left:1.2rem;">
|
||||
<?php foreach ($errors as $err): ?>
|
||||
<li><?= htmlspecialchars($err) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert-success">
|
||||
<strong>✓ <?php echo htmlspecialchars($message); ?></strong>
|
||||
</div>
|
||||
<div class="admin-alert admin-alert--success">✓ <?= htmlspecialchars($message) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="import.php" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<form action="import.php" method="post" enctype="multipart/form-data" class="admin-import-area">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
|
||||
<fieldset>
|
||||
<legend>Sélectionner un fichier CSV</legend>
|
||||
<div class="admin-form-row" style="align-items:start;">
|
||||
<label class="admin-label">Fichier CSV :</label>
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
<div class="admin-hint" style="margin-top:.5rem;">
|
||||
Colonnes attendues : 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>
|
||||
— Deux premières lignes ignorées (en-tête) — Séparateur : virgule — Encodage : UTF-8
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Format attendu:</strong></p>
|
||||
<ul>
|
||||
<li>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</li>
|
||||
<li>Les deux premières lignes seront ignorées (entête)</li>
|
||||
<li>Séparateur: virgule</li>
|
||||
<li>Encodage: UTF-8</li>
|
||||
</ul>
|
||||
|
||||
<label for="csv_file">Fichier CSV:</label>
|
||||
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
|
||||
<button type="submit">Importer</button>
|
||||
</fieldset>
|
||||
<div style="margin-top:1rem;">
|
||||
<button type="submit" class="admin-btn">Importer</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if (!empty($importResults)): ?>
|
||||
<h3>Résultats de l'import</h3>
|
||||
<div style="margin-top:2rem;">
|
||||
<h2 style="font-size:1rem;font-weight:600;margin-bottom:.75rem;color:var(--admin-text-muted);text-transform:uppercase;letter-spacing:.06em;">Résultats de l'import</h2>
|
||||
<div class="info-message">
|
||||
<pre><?php
|
||||
foreach ($importResults as $result) {
|
||||
echo htmlspecialchars($result) . "\n";
|
||||
}
|
||||
?></pre>
|
||||
<pre><?php foreach ($importResults as $r) echo htmlspecialchars($r) . "\n"; ?></pre>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Notes importantes</h3>
|
||||
<ul>
|
||||
<li><strong>Codes orientation:</strong> SC (Sculpture), VI (Vidéographie), CA (Cinéma d'animation), IP (Installation-Performance), etc.</li>
|
||||
<li><strong>Codes AP:</strong> DPM, LIENS, APS (comme dans la base)</li>
|
||||
<li><strong>Auteurs multiples:</strong> Séparer par des virgules</li>
|
||||
<li><strong>Mots-clés:</strong> Maximum 10, séparés par des virgules</li>
|
||||
<li><strong>Formats:</strong> Séparer par des virgules</li>
|
||||
<li>Les lignes avec erreurs seront ignorées et loggées</li>
|
||||
</ul>
|
||||
|
||||
<h3>Exemple de fichier CSV</h3>
|
||||
<p>Voir: <code>../db/Database_TFE_test.csv</code></p>
|
||||
</main>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
@@ -1,254 +1,196 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . "/../../config/bootstrap.php";
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
|
||||
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
||||
AdminAuth::requireLogin();
|
||||
|
||||
// Generate CSRF token
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
$pageTitle = "Liste des TFE";
|
||||
|
||||
require_once __DIR__ . '/../../src/Database.php';
|
||||
|
||||
try {
|
||||
$db = new Database();
|
||||
|
||||
// Get filter parameters
|
||||
$searchQuery = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
$yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null;
|
||||
$yearFilter = isset($_GET['year']) ? intval($_GET['year']) : null;
|
||||
$orientationFilter = isset($_GET['orientation']) ? intval($_GET['orientation']) : null;
|
||||
|
||||
$filters = [];
|
||||
if ($searchQuery) {
|
||||
$filters['search'] = $searchQuery;
|
||||
}
|
||||
if ($yearFilter) {
|
||||
$filters['year'] = $yearFilter;
|
||||
}
|
||||
if ($orientationFilter) {
|
||||
$filters['orientation'] = $orientationFilter;
|
||||
}
|
||||
if ($searchQuery) $filters['search'] = $searchQuery;
|
||||
if ($yearFilter) $filters['year'] = $yearFilter;
|
||||
if ($orientationFilter) $filters['orientation'] = $orientationFilter;
|
||||
|
||||
$theses = $db->getThesesList($filters);
|
||||
$years = $db->getAllYears();
|
||||
$theses = $db->getThesesList($filters);
|
||||
$years = $db->getAllYears();
|
||||
$orientations = $db->getAllOrientations();
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading theses list: " . $e->getMessage());
|
||||
die("Erreur lors du chargement de la liste.");
|
||||
}
|
||||
?>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
||||
|
||||
<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);
|
||||
});
|
||||
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';
|
||||
}
|
||||
function bulkAction(action) {
|
||||
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
|
||||
if (!checked.length) { alert('Sélectionnez au moins un TFE.'); return; }
|
||||
const word = action === 'publish' ? 'publier' : 'dépublier';
|
||||
if (!confirm(`${word.charAt(0).toUpperCase()+word.slice(1)} ${checked.length} TFE(s) ?`)) return;
|
||||
document.getElementById('bulk-action-input').value = action;
|
||||
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();
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.addEventListener('change', updateBulk));
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<main class="admin-main">
|
||||
<h1 class="admin-page-title">Liste des TFE</h1>
|
||||
|
||||
<?php if (isset($_SESSION['error'])): ?>
|
||||
<div class="alert-error">
|
||||
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($_SESSION['error']);
|
||||
unset($_SESSION['error']); ?>
|
||||
</div>
|
||||
<div class="admin-alert admin-alert--error">⚠ <?= htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['success'])): ?>
|
||||
<div class="alert-success">
|
||||
<strong>✓ <?php echo htmlspecialchars($_SESSION['success']);
|
||||
unset($_SESSION['success']); ?></strong>
|
||||
</div>
|
||||
<div class="admin-alert admin-alert--success">✓ <?= htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="bulk-actions" class="bulk-actions">
|
||||
<!-- Stats -->
|
||||
<div class="admin-stats">
|
||||
<div class="admin-stat">
|
||||
<div class="admin-stat__number"><?= count($theses) ?></div>
|
||||
<div class="admin-stat__label">TFE total</div>
|
||||
</div>
|
||||
<div class="admin-stat">
|
||||
<div class="admin-stat__number"><?= count(array_filter($theses, fn($t) => $t['is_published'])) ?></div>
|
||||
<div class="admin-stat__label">Publiés</div>
|
||||
</div>
|
||||
<div class="admin-stat">
|
||||
<div class="admin-stat__number"><?= count(array_filter($theses, fn($t) => !$t['is_published'])) ?></div>
|
||||
<div class="admin-stat__label">En attente</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<form class="admin-filters" method="get" action="/admin/">
|
||||
<input type="text" name="search" placeholder="Titre, auteur..."
|
||||
value="<?= htmlspecialchars($searchQuery) ?>">
|
||||
<select name="year">
|
||||
<option value="">Toutes les années</option>
|
||||
<?php foreach ($years as $y): ?>
|
||||
<option value="<?= $y ?>" <?= $yearFilter == $y ? 'selected' : '' ?>><?= $y ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="orientation">
|
||||
<option value="">Toutes les orientations</option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= $o['id'] ?>" <?= $orientationFilter == $o['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="admin-filters-btn">Filtrer</button>
|
||||
<?php if ($searchQuery || $yearFilter || $orientationFilter): ?>
|
||||
<a href="/admin/" class="admin-filters-reset">Réinitialiser</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<!-- Bulk actions bar -->
|
||||
<div id="bulk-actions" class="admin-bulk-actions">
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="bulk-form" method="post" action="actions/publish.php">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<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>
|
||||
|
||||
<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="/admin/">
|
||||
<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="/admin/">Réinitialiser</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<?php if (empty($theses)): ?>
|
||||
<p>Aucun TFE trouvé.</p>
|
||||
<p style="color:var(--admin-text-muted);padding:1rem 0;">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>
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" onchange="toggleAll(this)"></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" name="selected_theses[]" value="<?= $thesis['id'] ?>"></td>
|
||||
<td style="color:var(--admin-text-muted);font-size:.8rem;"><?= 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 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="admin-actions">
|
||||
<a href="/admin/thanks.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']): ?>
|
||||
<span class="status-badge status-published">Publié</span>
|
||||
<input type="hidden" name="action" value="unpublish">
|
||||
<button type="submit" class="admin-btn-sm admin-btn-unpublish"
|
||||
onclick="return confirm('Retirer de la publication ?')">Dépublier</button>
|
||||
<?php else: ?>
|
||||
<span class="status-badge status-pending">En attente</span>
|
||||
<input type="hidden" name="action" value="publish">
|
||||
<button type="submit" class="admin-btn-sm admin-btn-publish">Publier</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<a href="/admin/thanks.php?id=<?php echo $thesis['id']; ?>" class="btn btn-view">Voir</a>
|
||||
<a href="/admin/edit.php?id=<?php echo $thesis['id']; ?>" class="btn btn-edit">Éditer</a>
|
||||
<form method="post" action="actions/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>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
require_once __DIR__ . '/../../config/bootstrap.php';
|
||||
require_once __DIR__ . '/../../src/AdminAuth.php';
|
||||
|
||||
// If no password is configured, nothing to log into — go straight to admin.
|
||||
if (!defined('ADMIN_PASSWORD_HASH')) {
|
||||
header('Location: /admin/');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Already authenticated — redirect to admin.
|
||||
if (AdminAuth::isAuthenticated()) {
|
||||
header('Location: /admin/');
|
||||
exit;
|
||||
@@ -21,7 +18,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
header('Location: /admin/');
|
||||
exit;
|
||||
}
|
||||
// Intentionally vague error — avoid user-enumeration.
|
||||
$error = 'Mot de passe incorrect.';
|
||||
}
|
||||
|
||||
@@ -31,30 +27,32 @@ $pageTitle = 'Connexion';
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo htmlspecialchars($pageTitle); ?> — Post-ERG Admin</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Connexion – Posterg Admin</title>
|
||||
<link rel="stylesheet" href="/assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="/assets/admin.css">
|
||||
<link rel="shortcut icon" href="/assets/admin_favicon.svg" type="image/svg+xml">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><?php echo htmlspecialchars($pageTitle); ?></h1>
|
||||
</header>
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert-error">
|
||||
<strong>⚠️ <?php echo htmlspecialchars($error); ?></strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<form method="post" action="/admin/login.php">
|
||||
<fieldset>
|
||||
<legend>Authentification admin</legend>
|
||||
<label for="password">Mot de passe</label>
|
||||
<input type="password" id="password" name="password" required autofocus>
|
||||
<button type="submit">Se connecter</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</main>
|
||||
<body class="admin-body">
|
||||
<nav class="admin-nav">
|
||||
<span class="admin-nav__logo">Posterg</span>
|
||||
</nav>
|
||||
|
||||
<div class="admin-login-wrap">
|
||||
<div class="admin-login-box">
|
||||
<h2>Administration</h2>
|
||||
<?php if ($error): ?>
|
||||
<div class="admin-alert admin-alert--error">⚠ <?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="post" action="/admin/login.php" class="admin-form">
|
||||
<div class="admin-form-row" style="grid-template-columns:1fr;border:none;padding:.4rem 0;">
|
||||
<label class="admin-label" style="font-size:.82rem;color:var(--admin-text-muted);" for="password">Mot de passe</label>
|
||||
<input class="admin-input" type="password" id="password" name="password" required autofocus>
|
||||
</div>
|
||||
<div class="admin-submit-wrap" style="margin-top:1rem;padding-top:.5rem;">
|
||||
<button type="submit" class="admin-btn" style="width:100%;">Se connecter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -69,133 +69,93 @@ function formatFileSize($bytes) {
|
||||
}
|
||||
|
||||
// Set page title for header
|
||||
$pageTitle = "Merci";
|
||||
$pageTitle = "Récapitulatif TFE";
|
||||
?>
|
||||
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
||||
|
||||
<main>
|
||||
<?php if ($error): ?>
|
||||
<div class="error">
|
||||
<p>⚠️ <?php echo htmlspecialchars($error); ?></p>
|
||||
<p><a href="/admin/add.php">Retour au formulaire</a></p>
|
||||
</div>
|
||||
<main class="admin-main">
|
||||
<h1 class="admin-page-title">Récapitulatif TFE</h1>
|
||||
|
||||
<?php elseif ($thesis): ?>
|
||||
<p>d'avoir soumis votre TFE. Les informations ont été enregistrées et sont en attente de traitement.</p>
|
||||
<?php if ($error): ?>
|
||||
<div class="admin-alert admin-alert--error">⚠ <?= htmlspecialchars($error) ?></div>
|
||||
<p><a href="/admin/add.php" class="admin-btn-secondary">Retour au formulaire</a></p>
|
||||
|
||||
<div class="thesis-info">
|
||||
<h2>Récapitulatif de votre soumission</h2>
|
||||
|
||||
<h3>Informations de base</h3>
|
||||
<dl>
|
||||
<dt>Identifiant:</dt>
|
||||
<dd><strong><?php echo htmlspecialchars($thesis['identifier']); ?></strong></dd>
|
||||
|
||||
<dt>Titre:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['title']); ?></dd>
|
||||
|
||||
<?php if ($thesis['subtitle']): ?>
|
||||
<dt>Sous-titre:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['subtitle']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<dt>Auteur·ice(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['authors']); ?></dd>
|
||||
|
||||
<dt>Année:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['year']); ?></dd>
|
||||
</dl>
|
||||
|
||||
<h3>Détails académiques</h3>
|
||||
<dl>
|
||||
<dt>Orientation:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['orientation'] ?? 'Non spécifié'); ?></dd>
|
||||
|
||||
<dt>Atelier Pratique:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['ap_program'] ?? 'Non spécifié'); ?></dd>
|
||||
|
||||
<dt>Finalité:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['finality_type'] ?? 'Non spécifié'); ?></dd>
|
||||
|
||||
<?php if ($thesis['supervisors']): ?>
|
||||
<dt>Promoteur·ice(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['supervisors']); ?></dd>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
|
||||
<h3>Contenu</h3>
|
||||
<dl>
|
||||
<?php if ($thesis['synopsis']): ?>
|
||||
<dt>Synopsis:</dt>
|
||||
<dd><?php echo nl2br(htmlspecialchars($thesis['synopsis'])); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['languages']): ?>
|
||||
<dt>Langue(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['languages']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['formats']): ?>
|
||||
<dt>Format(s):</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['formats']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['keywords']): ?>
|
||||
<dt>Mots-clés:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['keywords']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['file_size_info']): ?>
|
||||
<dt>Durée/Taille:</dt>
|
||||
<dd><?php echo htmlspecialchars($thesis['file_size_info']); ?></dd>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($thesis['baiu_link']): ?>
|
||||
<dt>Lien:</dt>
|
||||
<dd><a href="<?php echo htmlspecialchars($thesis['baiu_link']); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo htmlspecialchars($thesis['baiu_link']); ?>
|
||||
</a></dd>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
|
||||
<?php if (!empty($files)): ?>
|
||||
<h3>Fichiers téléversés</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Nom du fichier</th>
|
||||
<th>Taille</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($files as $file): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($file['file_type']); ?></td>
|
||||
<td><?php echo htmlspecialchars($file['file_name']); ?></td>
|
||||
<td><?php echo formatFileSize($file['file_size']); ?></td>
|
||||
<td><?php echo date('d/m/Y H:i', strtotime($file['uploaded_at'])); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php elseif ($thesis): ?>
|
||||
<div class="admin-thesis-info">
|
||||
<h2>Informations de base</h2>
|
||||
<dl>
|
||||
<dt>Identifiant</dt><dd><?= htmlspecialchars($thesis['identifier']) ?></dd>
|
||||
<dt>Titre</dt><dd><?= htmlspecialchars($thesis['title']) ?></dd>
|
||||
<?php if ($thesis['subtitle']): ?>
|
||||
<dt>Sous-titre</dt><dd><?= htmlspecialchars($thesis['subtitle']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<dt>Auteur·ice(s)</dt><dd><?= htmlspecialchars($thesis['authors']) ?></dd>
|
||||
<dt>Année</dt><dd><?= htmlspecialchars($thesis['year']) ?></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<h3>Statut de publication</h3>
|
||||
<p><strong>⏳ En attente</strong> - Votre TFE ne sera publié qu'après la soutenance et l'ajout éventuel d'une note contextuelle par le jury.</p>
|
||||
<div class="admin-thesis-info">
|
||||
<h2>Détails académiques</h2>
|
||||
<dl>
|
||||
<dt>Orientation</dt><dd><?= htmlspecialchars($thesis['orientation'] ?? '–') ?></dd>
|
||||
<dt>Atelier pratique</dt><dd><?= htmlspecialchars($thesis['ap_program'] ?? '–') ?></dd>
|
||||
<dt>Finalité</dt><dd><?= htmlspecialchars($thesis['finality_type'] ?? '–') ?></dd>
|
||||
<?php if ($thesis['supervisors']): ?>
|
||||
<dt>Promoteur·ice(s)</dt><dd><?= htmlspecialchars($thesis['supervisors']) ?></dd>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<p class="submitted-date">
|
||||
Soumis le <?php echo date('d/m/Y à H:i', strtotime($thesis['submitted_at'])); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="admin-thesis-info">
|
||||
<h2>Contenu</h2>
|
||||
<dl>
|
||||
<?php if ($thesis['languages']): ?>
|
||||
<dt>Langue(s)</dt><dd><?= htmlspecialchars($thesis['languages']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<?php if ($thesis['formats']): ?>
|
||||
<dt>Format(s)</dt><dd><?= htmlspecialchars($thesis['formats']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<?php if ($thesis['keywords']): ?>
|
||||
<dt>Mots-clés</dt><dd><?= htmlspecialchars($thesis['keywords']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<?php if ($thesis['file_size_info']): ?>
|
||||
<dt>Durée / Taille</dt><dd><?= htmlspecialchars($thesis['file_size_info']) ?></dd>
|
||||
<?php endif; ?>
|
||||
<?php if ($thesis['baiu_link']): ?>
|
||||
<dt>Lien</dt><dd><a href="<?= htmlspecialchars($thesis['baiu_link']) ?>" target="_blank" rel="noopener"><?= htmlspecialchars($thesis['baiu_link']) ?></a></dd>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<p><a href="/admin/add.php">Soumettre un autre TFE</a></p>
|
||||
|
||||
<?php else: ?>
|
||||
<p>Aucune donnée à afficher.</p>
|
||||
<p><a href="/admin/add.php">Retour au formulaire</a></p>
|
||||
<?php if (!empty($files)): ?>
|
||||
<div class="admin-thesis-info">
|
||||
<h2>Fichiers</h2>
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Type</th><th>Fichier</th><th>Taille</th><th>Date</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($files as $f): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($f['file_type']) ?></td>
|
||||
<td><?= htmlspecialchars($f['file_name']) ?></td>
|
||||
<td><?= formatFileSize($f['file_size']) ?></td>
|
||||
<td><?= date('d/m/Y H:i', strtotime($f['uploaded_at'])) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<div style="margin-top:1.5rem;display:flex;gap:.75rem;flex-wrap:wrap;">
|
||||
<a href="/admin/edit.php?id=<?= $thesisId ?>" class="admin-btn">Modifier</a>
|
||||
<a href="/admin/add.php" class="admin-btn-secondary">Ajouter un autre TFE</a>
|
||||
<a href="/admin/" class="admin-btn-secondary">Retour à la liste</a>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<p style="color:var(--admin-text-muted);">Aucune donnée à afficher.</p>
|
||||
<p><a href="/admin/add.php" class="admin-btn-secondary">Retour au formulaire</a></p>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|
||||
|
||||
92
public/apropos.php
Normal file
92
public/apropos.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/bootstrap.php';
|
||||
$currentNav = 'apropos';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>À Propos – Posterg</title>
|
||||
<link rel="stylesheet" href="assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="assets/common.css">
|
||||
<link rel="stylesheet" href="assets/apropos.css">
|
||||
<?php if (php_sapi_name() === 'cli-server'): ?>
|
||||
<script>
|
||||
(function poll(){
|
||||
fetch('/live-reload.php').then(r=>r.json()).then(d=>{
|
||||
if(d.changed) location.reload(); else setTimeout(poll,1000);
|
||||
}).catch(()=>setTimeout(poll,2000));
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
<body class="apropos-body">
|
||||
|
||||
<?php include APP_ROOT . '/templates/nav.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/search-bar.php'; ?>
|
||||
|
||||
<main class="apropos-main">
|
||||
<div class="apropos-layout">
|
||||
|
||||
<!-- LEFT: main text -->
|
||||
<div class="apropos-left">
|
||||
<div class="apropos-description">
|
||||
<p>Ce site POSTERG a été créé pour répertorier et valoriser les mémoires de l'erg – École de Recherches Graphique de Bruxelles.</p>
|
||||
<p>L'objectif est à la fois d'offrir une vitrine aux projets des anciens étudiantes et de mettre en lumière la diversité des disciplines et des parcours qui façonnent l'histoire de l'école à travers les âges, depuis près de 100 ans.</p>
|
||||
</div>
|
||||
|
||||
<div class="apropos-licences">
|
||||
<h2>Licences</h2>
|
||||
<p>Les contenus de ce site sont publiés avec l'accord des auteur·ices et de l'erg. La reproduction des œuvres est soumise à l'autorisation de leurs auteur·ices respectif·ives.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: links, contacts, credits -->
|
||||
<div class="apropos-right">
|
||||
|
||||
<div>
|
||||
<h2 class="apropos-section-title">
|
||||
<a href="https://erg.be" target="_blank" rel="noopener">Site de l'erg</a>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="apropos-section-title">Contacts</h2>
|
||||
|
||||
<div class="apropos-contact">
|
||||
<span class="apropos-contact-name">Laurent Leprince</span>
|
||||
<span class="apropos-contact-role">Bibliothèque d'architecture, d'ingénierie architecturale, d'urbanisme (BAIU) :</span>
|
||||
<span class="apropos-contact-email">laurent.leprince@uclouvain.be</span>
|
||||
</div>
|
||||
|
||||
<div class="apropos-contact">
|
||||
<span class="apropos-contact-name">Xavier Gorgol</span>
|
||||
<span class="apropos-contact-role">Responsable des mémoires de l'ERG :</span>
|
||||
<span class="apropos-contact-email">xavier.gorgol@erg.be</span>
|
||||
</div>
|
||||
|
||||
<div class="apropos-contact">
|
||||
<span class="apropos-contact-name">Brigitte Ledune</span>
|
||||
<span class="apropos-contact-role">Cours de suivi de mémoire :</span>
|
||||
<span class="apropos-contact-email">brigitte.ledune@erg.be</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="apropos-section-title">Crédits</h2>
|
||||
<p class="apropos-credits-text">
|
||||
Design & développement : Olivia Marly, Théophile Gerveau-Mercie & Théo Hennequin
|
||||
</p>
|
||||
<p class="apropos-credits-text">
|
||||
Typographies : Ductus (Amélie Dumont) & BBB DM Sans
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,304 +1,473 @@
|
||||
/* ============================================================
|
||||
ADMIN SECTION
|
||||
============================================================ */
|
||||
|
||||
:root {
|
||||
--background-body: #3c856bff;
|
||||
--background: #161f27;
|
||||
--background-alt: #1a242f;
|
||||
--selection: #1c76c5;
|
||||
--text-main: #dbdbdb;
|
||||
--text-bright: #fff;
|
||||
--text-muted: #a9b1ba;
|
||||
--links: #41adff;
|
||||
--focus: #0096bfab;
|
||||
--border: ;
|
||||
--code: #ffbe85;
|
||||
--animation-duration: 0.1s;
|
||||
--button-base: #0c151c;
|
||||
--button-hover: #040a0f;
|
||||
--scrollbar-thumb: var(--button-hover);
|
||||
--scrollbar-thumb-hover: color-mod(var(--scrollbar-thumb) lightness(-30%));
|
||||
--form-placeholder: #a9a9a9;
|
||||
--form-text: #fff;
|
||||
--variable: #d941e2;
|
||||
--highlight: #efdb43;
|
||||
--select-arrow: svg-load("./assets/select-arrow.svg", fill: #efefef);
|
||||
--admin-bg: #1a1a1a;
|
||||
--admin-bg-alt: #242424;
|
||||
--admin-border: #333;
|
||||
--admin-text: #e8e8e8;
|
||||
--admin-text-muted: #888;
|
||||
--admin-purple: #9557b5;
|
||||
--admin-input-bg: transparent;
|
||||
}
|
||||
|
||||
/* @media (prefers-color-scheme: dark) { */
|
||||
/* --background-body: #202b38; */
|
||||
/* --background: #161f27; */
|
||||
/* --background-alt: #1a242f; */
|
||||
/* --selection: #1c76c5; */
|
||||
/* --text-main: #dbdbdb; */
|
||||
/* --text-bright: #fff; */
|
||||
/* --text-muted: #a9b1ba; */
|
||||
/* --links: #41adff; */
|
||||
/* --focus: #0096bfab; */
|
||||
/* --border: #526980; */
|
||||
/* --code: #ffbe85; */
|
||||
/* --animation-duration: 0.1s; */
|
||||
/* --button-base: #0c151c; */
|
||||
/* --button-hover: #040a0f; */
|
||||
/* --scrollbar-thumb: var(--button-hover); */
|
||||
/* --scrollbar-thumb-hover: color-mod(var(--scrollbar-thumb) lightness(-30%)); */
|
||||
/* --form-placeholder: #a9a9a9; */
|
||||
/* --form-text: #fff; */
|
||||
/* --variable: #d941e2; */
|
||||
/* --highlight: #efdb43; */
|
||||
/* --select-arrow: svg-load("./assets/select-arrow.svg", fill: #efefef); */
|
||||
/* } */
|
||||
|
||||
/* Base Styles */
|
||||
body {
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
background-color: var(--background-body);
|
||||
color: var(--text-main);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
header, main, footer {
|
||||
margin: auto 2ch;
|
||||
}
|
||||
|
||||
header, footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 2ch;
|
||||
max-width: 80vw;
|
||||
margin: auto 1.2rem;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
}
|
||||
table {
|
||||
table-layout: auto;
|
||||
margin: auto;
|
||||
width: 95vw;
|
||||
margin-left: calc(50% - 45vw);
|
||||
/* margin: auto; */
|
||||
}
|
||||
table {
|
||||
width: max-content;
|
||||
min-width: 95vw; /* optional fallback */
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/questions/3084261/alternate-table-row-color-using-css */
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #204639;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: #2f6a55;
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid;
|
||||
border-radius: 6px;
|
||||
html, body {
|
||||
margin: 0;
|
||||
margin-bottom: 12px;
|
||||
padding: 1em;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-size: 0.9em;
|
||||
.admin-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: var(--admin-bg);
|
||||
color: var(--admin-text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Admin nav (dark version of site-nav) */
|
||||
.admin-nav {
|
||||
background: linear-gradient(to bottom, var(--admin-purple) 0%, rgba(149, 87, 181, 0.0) 100%);
|
||||
padding: 0.55rem 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.admin-nav__logo {
|
||||
font-size: 0.88rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.admin-nav__link {
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.admin-nav__link:hover,
|
||||
.admin-nav__link.active {
|
||||
opacity: 1;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
/* Main content area */
|
||||
.admin-main {
|
||||
flex: 1;
|
||||
padding: 2.5rem 2rem 4rem;
|
||||
max-width: 1100px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-page-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--admin-text);
|
||||
margin: 0 0 2.5rem 0;
|
||||
}
|
||||
|
||||
label {
|
||||
vertical-align: middle;
|
||||
margin-bottom: 1em;
|
||||
display: inline-block;
|
||||
/* ---- Forms ---- */
|
||||
.admin-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
select,
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"],
|
||||
input[type="checkbox"],
|
||||
input[type="range"],
|
||||
input[type="radio"] {
|
||||
.admin-form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr;
|
||||
align-items: start;
|
||||
border-top: 1px solid var(--admin-border);
|
||||
padding: 0.75rem 0;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.admin-form-row:last-of-type {
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
}
|
||||
|
||||
.admin-label {
|
||||
font-size: 0.92rem;
|
||||
color: var(--admin-text);
|
||||
padding-top: 0.5rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.admin-input,
|
||||
.admin-select,
|
||||
.admin-textarea {
|
||||
width: 100%;
|
||||
background: var(--admin-input-bg);
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
color: var(--admin-text);
|
||||
font-size: 0.92rem;
|
||||
font-family: inherit;
|
||||
padding: 0.4rem 0;
|
||||
outline: none;
|
||||
border-radius: 0;
|
||||
transition: border-color 0.15s;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.admin-input:focus,
|
||||
.admin-select:focus,
|
||||
.admin-textarea:focus {
|
||||
border-bottom-color: var(--admin-purple);
|
||||
}
|
||||
|
||||
.admin-input::placeholder,
|
||||
.admin-textarea::placeholder {
|
||||
color: var(--admin-text-muted);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.admin-textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Select custom arrow */
|
||||
.admin-select {
|
||||
cursor: pointer;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23888' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0 center;
|
||||
padding-right: 1.2rem;
|
||||
}
|
||||
|
||||
.admin-select option {
|
||||
background: var(--admin-bg);
|
||||
color: var(--admin-text);
|
||||
}
|
||||
|
||||
/* File inputs */
|
||||
.admin-file-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.admin-file-input input[type="file"] {
|
||||
font-size: 0.85rem;
|
||||
color: var(--admin-text-muted);
|
||||
background: transparent;
|
||||
border: 1px dashed var(--admin-border);
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.admin-file-input input[type="file"]:hover {
|
||||
border-color: var(--admin-purple);
|
||||
}
|
||||
|
||||
.admin-hint {
|
||||
font-size: 0.78rem;
|
||||
color: var(--admin-text-muted);
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
/* Checkboxes & radios */
|
||||
.admin-checkbox-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
padding-top: 0.3rem;
|
||||
}
|
||||
|
||||
.admin-checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--admin-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
display: block;
|
||||
.admin-checkbox-label input[type="checkbox"] {
|
||||
accent-color: var(--admin-purple);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
color: var(--form-text);
|
||||
background-color: var(--background);
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
padding: 10px;
|
||||
/* Submit button */
|
||||
.admin-submit-wrap {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-btn {
|
||||
padding: 0.65rem 2.5rem;
|
||||
background: var(--admin-purple);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
border-radius: 3px;
|
||||
font-size: 0.92rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.04em;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"] {
|
||||
background-color: var(--button-base);
|
||||
padding-right: 30px;
|
||||
padding-left: 30px;
|
||||
.admin-btn:hover {
|
||||
background: #7b3fa0;
|
||||
}
|
||||
|
||||
/* Alert Messages */
|
||||
.error-message,
|
||||
.alert-error {
|
||||
background: #fee;
|
||||
border: 2px solid #c00;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
color: #c00;
|
||||
.admin-btn-secondary {
|
||||
padding: 0.5rem 1.5rem;
|
||||
background: transparent;
|
||||
color: var(--admin-text-muted);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 3px;
|
||||
font-size: 0.88rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.04em;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.success-message,
|
||||
.alert-success {
|
||||
background: #efe;
|
||||
border: 2px solid #0a0;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
color: #0a0;
|
||||
.admin-btn-secondary:hover {
|
||||
border-color: var(--admin-text-muted);
|
||||
color: var(--admin-text);
|
||||
}
|
||||
|
||||
.info-message {
|
||||
background: #f5f5f5;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
/* ---- Alert Messages ---- */
|
||||
.admin-alert {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-left: 3px solid;
|
||||
}
|
||||
|
||||
.info-message pre {
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
.admin-alert--error {
|
||||
background: rgba(200, 0, 0, 0.1);
|
||||
border-color: #c00;
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
/* ul.no-style { */
|
||||
/* list-style: none; */
|
||||
/* } */
|
||||
|
||||
/* Filters */
|
||||
.filters {
|
||||
padding: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 4px;
|
||||
.admin-alert--success {
|
||||
background: rgba(0, 150, 80, 0.1);
|
||||
border-color: #0a0;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.filters form {
|
||||
/* ---- Stats cards ---- */
|
||||
.admin-stats {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.filters fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.admin-stat {
|
||||
background: var(--admin-bg-alt);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 4px;
|
||||
padding: 1rem 1.5rem;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.admin-stat__number {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--admin-purple);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.admin-stat__label {
|
||||
font-size: 0.82rem;
|
||||
color: var(--admin-text-muted);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* ---- Filters bar ---- */
|
||||
.admin-filters {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.admin-filters input[type="text"],
|
||||
.admin-filters select {
|
||||
background: var(--admin-bg-alt);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 3px;
|
||||
color: var(--admin-text);
|
||||
font-size: 0.88rem;
|
||||
font-family: inherit;
|
||||
padding: 0.45rem 0.75rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-filters input[type="text"]:focus,
|
||||
.admin-filters select:focus {
|
||||
border-color: var(--admin-purple);
|
||||
}
|
||||
|
||||
.admin-filters-btn {
|
||||
padding: 0.45rem 1rem;
|
||||
background: var(--admin-purple);
|
||||
color: #fff;
|
||||
border: none;
|
||||
min-width: 200px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.88rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.thesis-table {
|
||||
.admin-filters-reset {
|
||||
font-size: 0.88rem;
|
||||
color: var(--admin-text-muted);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ---- Table ---- */
|
||||
.admin-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.thesis-table th,
|
||||
.thesis-table td {
|
||||
padding: 0.75rem;
|
||||
.admin-table th {
|
||||
text-align: left;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--admin-text-muted);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.thesis-table th {
|
||||
font-weight: bold;
|
||||
.admin-table td {
|
||||
padding: 0.65rem 0.75rem;
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
color: var(--admin-text);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.thesis-title {
|
||||
font-weight: bold;
|
||||
.admin-table tr:hover td {
|
||||
background: var(--admin-bg-alt);
|
||||
}
|
||||
|
||||
.thesis-subtitle {
|
||||
.admin-table .thesis-title {
|
||||
font-weight: 500;
|
||||
color: var(--admin-text);
|
||||
}
|
||||
|
||||
.admin-table .thesis-subtitle {
|
||||
font-size: 0.82rem;
|
||||
color: var(--admin-text-muted);
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
/* Status badges */
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #ffd700;
|
||||
color: #000;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.status-published {
|
||||
background: #90ee90;
|
||||
color: #000;
|
||||
background: rgba(0, 150, 80, 0.15);
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.actions {
|
||||
.status-pending {
|
||||
background: rgba(255, 200, 0, 0.12);
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
/* Action buttons in table */
|
||||
.admin-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.35rem 0.75rem;
|
||||
.admin-btn-sm {
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.78rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 0.9em;
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-view {
|
||||
background: #4a90e2;
|
||||
color: white;
|
||||
.admin-btn-view {
|
||||
background: rgba(65, 173, 255, 0.15);
|
||||
color: #41adff;
|
||||
border-color: rgba(65, 173, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #f39c12;
|
||||
color: white;
|
||||
.admin-btn-view:hover {
|
||||
background: rgba(65, 173, 255, 0.25);
|
||||
}
|
||||
|
||||
.btn-publish {
|
||||
background: #27ae60;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
.admin-btn-edit {
|
||||
background: rgba(243, 156, 18, 0.15);
|
||||
color: #f39c12;
|
||||
border-color: rgba(243, 156, 18, 0.3);
|
||||
}
|
||||
|
||||
.btn-unpublish {
|
||||
background: #95a5a6;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
.admin-btn-edit:hover {
|
||||
background: rgba(243, 156, 18, 0.25);
|
||||
}
|
||||
|
||||
.admin-btn-publish {
|
||||
background: rgba(0, 150, 80, 0.15);
|
||||
color: #4caf50;
|
||||
border-color: rgba(0, 150, 80, 0.3);
|
||||
border: 1px solid rgba(0, 150, 80, 0.3);
|
||||
}
|
||||
|
||||
.admin-btn-publish:hover {
|
||||
background: rgba(0, 150, 80, 0.25);
|
||||
}
|
||||
|
||||
.admin-btn-unpublish {
|
||||
background: rgba(149, 165, 166, 0.15);
|
||||
color: #95a5a6;
|
||||
border-color: rgba(149, 165, 166, 0.3);
|
||||
border: 1px solid rgba(149, 165, 166, 0.3);
|
||||
}
|
||||
|
||||
.admin-btn-unpublish:hover {
|
||||
background: rgba(149, 165, 166, 0.25);
|
||||
}
|
||||
|
||||
.publish-form {
|
||||
@@ -306,162 +475,112 @@ input[type="button"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Statistics */
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: darkslateblue;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Bulk Actions */
|
||||
.bulk-actions {
|
||||
background: #f5f5f5;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
/* Bulk actions */
|
||||
.admin-bulk-actions {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.6rem 1rem;
|
||||
background: var(--admin-bg-alt);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.bulk-actions-buttons {
|
||||
.admin-bulk-btns {
|
||||
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;
|
||||
}
|
||||
|
||||
#bulk-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#bulk-form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Thesis Info (Thanks page) */
|
||||
.thesis-info {
|
||||
/* background: #f5f5f5; */
|
||||
border: 1px white solid;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.thesis-info h2 {
|
||||
margin-top: 0;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.thesis-info h3 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
/* color: #555; */
|
||||
}
|
||||
|
||||
.thesis-info dl {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr;
|
||||
gap: 0.5rem 1rem;
|
||||
/* Thesis info (thanks page) */
|
||||
.admin-thesis-info {
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 6px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.thesis-info dt {
|
||||
font-weight: bold;
|
||||
/* color: #666; */
|
||||
.admin-thesis-info h2 {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1.2rem;
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.thesis-info dd {
|
||||
.admin-thesis-info dl {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr;
|
||||
gap: 0.4rem 1rem;
|
||||
}
|
||||
|
||||
.admin-thesis-info dt {
|
||||
font-weight: 600;
|
||||
font-size: 0.88rem;
|
||||
color: var(--admin-text-muted);
|
||||
}
|
||||
|
||||
.admin-thesis-info dd {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.thesis-info table {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
/* Info/error messages */
|
||||
.info-message {
|
||||
background: var(--admin-bg-alt);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.thesis-info table th {
|
||||
text-align: left;
|
||||
background: #ddd;
|
||||
padding: 0.5rem;
|
||||
.info-message pre {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.thesis-info table td {
|
||||
padding: 0.5rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.submitted-date {
|
||||
margin-top: 2rem;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fee;
|
||||
border: 2px solid #c00;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
/* Form Elements */
|
||||
label.checkbox-label {
|
||||
/* Login page */
|
||||
.admin-login-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.thesis-info dl {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.thesis-info dt {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.admin-login-box {
|
||||
background: var(--admin-bg-alt);
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 6px;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.admin-login-box h2 {
|
||||
margin: 0 0 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.admin-login-box .admin-form-row {
|
||||
grid-template-columns: 1fr;
|
||||
border: none;
|
||||
padding: 0.4rem 0;
|
||||
}
|
||||
|
||||
.admin-login-box .admin-label {
|
||||
font-size: 0.82rem;
|
||||
color: var(--admin-text-muted);
|
||||
padding: 0;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
/* Import page */
|
||||
.admin-import-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
139
public/assets/apropos.css
Normal file
139
public/assets/apropos.css
Normal file
@@ -0,0 +1,139 @@
|
||||
/* ============================================================
|
||||
À PROPOS PAGE (apropos.php)
|
||||
============================================================ */
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.apropos-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.apropos-main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 2.5rem 1.5rem 4rem;
|
||||
}
|
||||
|
||||
/* Two-column layout */
|
||||
.apropos-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 1fr;
|
||||
gap: 4rem;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
/* Left col — main description text in big monospace */
|
||||
.apropos-left {}
|
||||
|
||||
.apropos-description {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 1.55rem;
|
||||
line-height: 1.45;
|
||||
color: var(--black);
|
||||
font-weight: 400;
|
||||
margin: 0 0 2rem 0;
|
||||
}
|
||||
|
||||
.apropos-description p {
|
||||
margin: 0 0 1.2em 0;
|
||||
}
|
||||
|
||||
/* Right col — links, contacts, credits */
|
||||
.apropos-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.apropos-section-title {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 1.55rem;
|
||||
font-weight: 400;
|
||||
color: var(--black);
|
||||
margin: 0 0 0.5rem 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.apropos-section-title a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.apropos-contact {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.apropos-contact-name {
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
color: var(--black);
|
||||
display: block;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.apropos-contact-role,
|
||||
.apropos-contact-email {
|
||||
font-size: 0.9rem;
|
||||
color: var(--black);
|
||||
line-height: 1.4;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.apropos-credits-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--black);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Licences section */
|
||||
.apropos-licences {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.apropos-licences h2 {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 1.55rem;
|
||||
font-weight: 400;
|
||||
margin: 0 0 0.75rem 0;
|
||||
}
|
||||
|
||||
.apropos-licences p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--black);
|
||||
line-height: 1.6;
|
||||
margin: 0 0 0.75rem 0;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 900px) {
|
||||
.apropos-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.apropos-description {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.apropos-section-title {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.apropos-main {
|
||||
padding: 1.5rem 1rem 3rem;
|
||||
}
|
||||
|
||||
.apropos-description {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,104 +1,135 @@
|
||||
@font-face {
|
||||
font-family: police1;
|
||||
font-family: "police1";
|
||||
src: url("./fonts/Combinedd.otf");
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
/* UTILE POUR FORCER UN MODE LIGHT */
|
||||
/* @media (prefers-color-scheme: dark) { */
|
||||
/* :root, */
|
||||
/* ::backdrop { */
|
||||
/* --bg: #fff; */
|
||||
/* --accent-bg: #f5f7ff; */
|
||||
/* --text: #212121; */
|
||||
/* --text-light: #585858; */
|
||||
/* --border: #898ea4; */
|
||||
/* --accent: #0d47a1; */
|
||||
/* --code: #d81b60; */
|
||||
/* --preformatted: #444; */
|
||||
/* --marked: #ffdd33; */
|
||||
/* --disabled: #efefef; */
|
||||
/* } */
|
||||
/* } */
|
||||
/* ============================================================
|
||||
SHARED VARIABLES & RESET
|
||||
============================================================ */
|
||||
:root {
|
||||
--purple: #9557b5;
|
||||
--purple-dark: #7b3fa0;
|
||||
--purple-light: rgba(149, 87, 181, 0.12);
|
||||
--black: #111;
|
||||
--white: #fff;
|
||||
--grey-light: #f5f5f5;
|
||||
--border-color: #ddd;
|
||||
--text-muted: #666;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* FORMULAIRE */
|
||||
form label {
|
||||
font-family: police1;
|
||||
font-size: 1rem;
|
||||
}
|
||||
form input,
|
||||
select,
|
||||
textarea {
|
||||
border-color: #c104fc;
|
||||
overflow: visible;
|
||||
outline: none;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
form input:focus,
|
||||
select:focus {
|
||||
border: 3px solid rgba(77, 168, 112, 1);
|
||||
}
|
||||
|
||||
label {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
input {
|
||||
/* font-family: police1; */
|
||||
/* font-weight: bold; */
|
||||
background-color: none;
|
||||
color: rgb(193, 4, 252);
|
||||
border: 1px solid rgb(193, 4, 252);
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
background: var(--white);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(193, 4, 252);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
a, a:visited {
|
||||
color: rgb(193, 4, 252);
|
||||
|
||||
/* ============================================================
|
||||
NAV BAR (shared across all public pages)
|
||||
============================================================ */
|
||||
.site-nav {
|
||||
background: linear-gradient(to bottom, var(--purple) 0%, rgba(149, 87, 181, 0.0) 100%);
|
||||
padding: 0.55rem 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
input:active {
|
||||
border-color: rgba(77, 168, 112, 1);
|
||||
.site-nav__logo {
|
||||
font-family: "police1", sans-serif;
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
button,
|
||||
[role="button"],
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"],
|
||||
label[type="button"] {
|
||||
background-color: white;
|
||||
margin-top: 2rem;
|
||||
font-size: 16px;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
.site-nav__links {
|
||||
display: flex;
|
||||
gap: 3rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
button {}
|
||||
|
||||
/* For Google Chrome, Safari, and newer versions of Opera */
|
||||
::placeholder {
|
||||
/* color: rgb(213, 73, 255); */
|
||||
font-size: 0.8rem;
|
||||
.site-nav__link {
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
opacity: 0.92;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
/* For Mozilla Firefox */
|
||||
::-moz-placeholder {
|
||||
/* color: rgb(213, 73, 255); */
|
||||
font-size: 0.8rem;
|
||||
.site-nav__link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.site-nav__right {
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
opacity: 0.92;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.site-nav__right:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
SEARCH BAR (shared)
|
||||
============================================================ */
|
||||
.site-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--white);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.site-search__icon {
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.site-search__input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 0.95rem;
|
||||
color: var(--black);
|
||||
background: transparent;
|
||||
padding: 0.15rem 0;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.site-search__input::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@@ -1,350 +1,152 @@
|
||||
body {
|
||||
/* ============================================================
|
||||
HOME PAGE (index.php)
|
||||
============================================================ */
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.home-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
Arial, sans-serif;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
/* Layout ratios: header 2/5, main 3/5, footer 1/5 */
|
||||
header {
|
||||
flex: 2;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 7;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
/* Cards grid — scrollable main area */
|
||||
.home-main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
header, main, footer {
|
||||
padding: 1rem;
|
||||
margin: 0;
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
header {
|
||||
font-family: "police1", sans-serif;
|
||||
background: #9557b5ff;
|
||||
color: white;
|
||||
display: flex;
|
||||
gap: 4%;
|
||||
padding: 1.5rem 3rem;
|
||||
align-items: center;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
header .title {
|
||||
color: white;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
header section {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
header section p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
header section p:first-child {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
header section p:not(:first-child) {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
header nav {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
header nav button {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.9rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
header nav button:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
header nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Filter info banner */
|
||||
.filter-info {
|
||||
background: rgba(149, 87, 181, 0.9);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.clear-filter {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 15px;
|
||||
font-size: 0.85rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.clear-filter:hover {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
main {
|
||||
background: #3c856bff;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cards-container {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0.5rem;
|
||||
overflow: hidden;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* Default: 3 rows × 4 columns = 12 items */
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
|
||||
/* Small screens: 2 rows × 3 columns = 6 items */
|
||||
@media (max-width: 1200px) {
|
||||
@media (min-width: 1400px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* Medium screens: 3 rows × 4 columns = 12 items */
|
||||
@media (min-width: 1201px) and (max-width: 1500px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Large screens: 3 rows × 6 columns = 18 items */
|
||||
@media (min-width: 1701px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra large screens: 4 rows × 6 columns = 24 items */
|
||||
@media (min-width: 2000px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(4, 1fr);
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile placeholder (will adjust later) */
|
||||
@media (max-width: 768px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Each card = media thumbnail + text below */
|
||||
.card-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #eee;
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: var(--white);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card__media {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
aspect-ratio: 4/3;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
background: #e8e8e8;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
.card__media img,
|
||||
.card__media video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card .title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
.card:hover .card__media img,
|
||||
.card:hover .card__media video {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.card__media--placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #e8e8e8, #d0d0d0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #aaa;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.card__info {
|
||||
padding: 0.55rem 0.5rem 0.65rem;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.35;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.card__info .authors {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.card__info .title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
color: var(--text-muted);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card .authors {
|
||||
font-size: 0.65rem;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card .year {
|
||||
font-size: 0.65rem;
|
||||
margin: 0;
|
||||
color: #9557b5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card .tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.2rem;
|
||||
margin-top: auto;
|
||||
padding-top: 0.2rem;
|
||||
}
|
||||
|
||||
.card .tag {
|
||||
font-size: 0.55rem;
|
||||
padding: 0.15rem 0.25rem;
|
||||
background: rgba(149, 87, 181, 0.15);
|
||||
color: #7a3d95;
|
||||
border-radius: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: #222222ff;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Years navigation in footer */
|
||||
.years-nav {
|
||||
height: 100%;
|
||||
/* Filter info */
|
||||
.filter-info {
|
||||
background: var(--purple-light);
|
||||
color: var(--purple);
|
||||
padding: 0.4rem 1.5rem;
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.years-scroll {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
padding: 0.75rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Hide scrollbar but keep functionality */
|
||||
.years-scroll::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.years-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.years-scroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.year-link {
|
||||
color: white;
|
||||
.clear-filter {
|
||||
color: var(--purple);
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
transition: background 0.2s;
|
||||
font-size: 1rem;
|
||||
padding: 0.15rem 0.6rem;
|
||||
background: rgba(149, 87, 181, 0.12);
|
||||
border-radius: 3px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.year-link:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
.clear-filter:hover {
|
||||
background: rgba(149, 87, 181, 0.22);
|
||||
}
|
||||
|
||||
.year-link.active {
|
||||
background: #9557b5ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Pagination controls */
|
||||
.pagination {
|
||||
/* Pagination */
|
||||
.pagination-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
margin-top: 1rem;
|
||||
width: fit-content;
|
||||
align-self: center;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
background: var(--white);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -352,24 +154,20 @@ footer {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
padding: 0 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #3c856b;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
color: var(--black);
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.pagination-btn:hover:not(.disabled) {
|
||||
background: white;
|
||||
border-color: #9557b5;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--purple);
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
.pagination-btn.disabled {
|
||||
@@ -379,32 +177,12 @@ footer {
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0 1rem;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.page-current {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: #9557b5;
|
||||
background: white;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
min-width: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-separator {
|
||||
font-weight: 300;
|
||||
opacity: 0.6;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.page-total {
|
||||
opacity: 0.8;
|
||||
font-weight: 600;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
@@ -1,452 +1,289 @@
|
||||
/* Search page - horizontal layout */
|
||||
body {
|
||||
/* ============================================================
|
||||
RÉPERTOIRE / SEARCH PAGE (search.php)
|
||||
============================================================ */
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.search-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.search-main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ---- 4-column index layout ---- */
|
||||
.repertoire-index {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 2fr 1.5fr;
|
||||
gap: 0;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
Arial, sans-serif;
|
||||
padding: 0 1.5rem;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* Layout: header sidebar, main+footer wrapper takes rest */
|
||||
header {
|
||||
flex: 0 0 250px;
|
||||
min-width: 0;
|
||||
max-width: 250px;
|
||||
height: 100vh;
|
||||
font-family: "police1", sans-serif;
|
||||
background: #9557b5ff;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
padding: 0.75rem 0.6rem;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 40px;
|
||||
.repertoire-col {
|
||||
padding: 0.75rem 0.5rem 2rem;
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
.repertoire-col:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: #3c856bff;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 40px;
|
||||
.repertoire-col__header {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
footer {
|
||||
background: #222222ff;
|
||||
padding: 0.75rem 1rem;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
/* Search header */
|
||||
.search-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
padding: 0.35rem 0.6rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 0.35rem 0.6rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
font-size: 0.8rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #999;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.search-button,
|
||||
.filter-button {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.search-button:hover,
|
||||
.filter-button:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.filter-button.active {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Filters panel */
|
||||
.filters-panel {
|
||||
display: none;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
padding: 0.4rem;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.filters-panel.show {
|
||||
/* Years column - big bold numbers */
|
||||
.year-index-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.filters-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.65rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 0.3rem 0.35rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
font-size: 0.7rem;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
margin-top: 0.3rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reset-button:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: rgba(255, 100, 100, 0.2);
|
||||
color: white;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Main content - results grid */
|
||||
.cards-container {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0.5rem;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
|
||||
@media (min-width: 1400px) and (max-width: 1700px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1701px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(4, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1399px) {
|
||||
.cards-container {
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.card-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #eee;
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card .title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
color: #333;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card .authors {
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card .year {
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
color: #9557b5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card .tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
margin-top: auto;
|
||||
padding-top: 0.3rem;
|
||||
}
|
||||
|
||||
.card .tag {
|
||||
font-size: 0.65rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
background: rgba(149, 87, 181, 0.15);
|
||||
color: #7a3d95;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 8px;
|
||||
margin-top: 0.75rem;
|
||||
width: fit-content;
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #3c856b;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.pagination-btn:hover:not(.disabled) {
|
||||
background: white;
|
||||
border-color: #9557b5;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pagination-btn.disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0 0.5rem;
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.page-current {
|
||||
font-size: 1.1rem;
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
color: #9557b5;
|
||||
background: white;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 6px;
|
||||
min-width: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-separator {
|
||||
font-weight: 300;
|
||||
opacity: 0.6;
|
||||
padding: 0 0.15rem;
|
||||
}
|
||||
|
||||
.page-total {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Footer - results count */
|
||||
.results-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
gap: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.results-count {
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.1;
|
||||
color: var(--black);
|
||||
text-decoration: none;
|
||||
padding: 0.1rem 0;
|
||||
transition: color 0.15s;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.year-index-item:hover,
|
||||
.year-index-item.active {
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* Categories column */
|
||||
.cat-index-group {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.cat-index-label {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
display: block;
|
||||
margin-bottom: 0.15rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.cat-index-item {
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
color: var(--black);
|
||||
text-decoration: none;
|
||||
padding: 0.1rem 0;
|
||||
line-height: 1.4;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.cat-index-item:hover,
|
||||
.cat-index-item.active {
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* Students column */
|
||||
.student-index-item {
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
color: var(--black);
|
||||
text-decoration: none;
|
||||
padding: 0.1rem 0;
|
||||
line-height: 1.4;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.student-index-item:hover {
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* Keywords column */
|
||||
.keyword-index-item {
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
color: var(--black);
|
||||
text-decoration: none;
|
||||
padding: 0.1rem 0;
|
||||
line-height: 1.4;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.keyword-index-item:hover,
|
||||
.keyword-index-item.active {
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* ---- Search results view (grid) ---- */
|
||||
.search-results-view {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.search-results-header {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.results-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.result-card__authors {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.result-card__title {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.35;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.result-card__meta {
|
||||
font-size: 0.78rem;
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* Toggle button (index/results) */
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.view-toggle__btn {
|
||||
padding: 0.25rem 0.75rem;
|
||||
font-size: 0.78rem;
|
||||
background: var(--white);
|
||||
color: var(--text-muted);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.view-toggle__btn.active,
|
||||
.view-toggle__btn:hover {
|
||||
background: var(--purple);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
/* Search controls bar */
|
||||
.search-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.4rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.search-filter-label {
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-filter-select {
|
||||
font-size: 0.82rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
background: var(--white);
|
||||
color: var(--black);
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-filter-select:focus {
|
||||
border-color: var(--purple);
|
||||
}
|
||||
|
||||
.search-apply-btn {
|
||||
font-size: 0.82rem;
|
||||
padding: 0.2rem 0.8rem;
|
||||
background: var(--purple);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.search-apply-btn:hover {
|
||||
background: var(--purple-dark);
|
||||
}
|
||||
|
||||
.search-reset-link {
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-empty {
|
||||
padding: 3rem 1.5rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Error message */
|
||||
.search-error {
|
||||
background: #fff0f0;
|
||||
border-left: 3px solid #c00;
|
||||
color: #c00;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.88rem;
|
||||
margin: 0.5rem 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1,389 +1,172 @@
|
||||
/* TFE (Thesis) Page Styling */
|
||||
/* ============================================================
|
||||
TFE INDIVIDUAL PAGE (tfe.php)
|
||||
============================================================ */
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tfe-body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.tfe-header {
|
||||
flex: 2;
|
||||
min-height: 0;
|
||||
background: #9557b5;
|
||||
color: white;
|
||||
padding: 1.5rem 2rem;
|
||||
margin: 0;
|
||||
border-radius: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tfe-title {
|
||||
font-family: "police1", sans-serif;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 0.5rem 0;
|
||||
line-height: 1.2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tfe-subtitle {
|
||||
font-size: 1.15rem;
|
||||
margin: 0 0 1rem 0;
|
||||
opacity: 0.9;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.header-metadata {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.meta-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.95rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.meta-group.keywords {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.meta-group .label {
|
||||
font-weight: 600;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.meta-group .author {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.meta-group .separator {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.meta-group .year {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.tfe-main {
|
||||
flex: 7;
|
||||
min-height: 0;
|
||||
background: #3c856b;
|
||||
padding: 2rem;
|
||||
margin: 0;
|
||||
border-radius: 40px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 2rem 1.5rem 3rem;
|
||||
}
|
||||
|
||||
.tfe-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Two-column layout */
|
||||
.tfe-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.4fr;
|
||||
gap: 3rem;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
/* Left column */
|
||||
.tfe-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* Student name — large */
|
||||
.tfe-author {
|
||||
font-size: 1.9rem;
|
||||
font-weight: 400;
|
||||
color: var(--black);
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* Title — very large, slightly spaced */
|
||||
.tfe-title {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 400;
|
||||
color: var(--black);
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* Metadata list */
|
||||
.tfe-meta-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tfe-meta-item {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tfe-meta-item .label {
|
||||
color: var(--black);
|
||||
font-weight: 400;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tfe-meta-item .value {
|
||||
color: var(--black);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tfe-meta-item .value a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Synopsis text */
|
||||
.tfe-synopsis-text {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.7;
|
||||
color: var(--black);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Right column — media */
|
||||
.tfe-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tfe-media-block {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tfe-media-block img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tfe-media-block embed,
|
||||
.tfe-media-block video {
|
||||
width: 100%;
|
||||
display: block;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tfe-media-block video {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.tfe-media-block embed {
|
||||
height: 700px;
|
||||
}
|
||||
|
||||
.tfe-file-caption {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
margin: 0.3rem 0 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tfe-no-files {
|
||||
font-size: 0.95rem;
|
||||
color: var(--text-muted);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 900px) {
|
||||
.tfe-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Metadata Section */
|
||||
.tfe-metadata {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.25rem;
|
||||
color: #666;
|
||||
margin: 0 0 1.5rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.metadata-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.metadata-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.meta-item strong {
|
||||
color: #9557b5;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.meta-item span {
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.context-note {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: #f8f8f8;
|
||||
border-left: 4px solid #9557b5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.context-note em {
|
||||
color: #555;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Synopsis Section */
|
||||
.tfe-synopsis {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.tfe-synopsis h3 {
|
||||
.tfe-author {
|
||||
font-size: 1.5rem;
|
||||
color: #9557b5;
|
||||
margin: 0 0 1rem 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tfe-title {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.synopsis-content {
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Files Section */
|
||||
.tfe-files {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.file-block {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-block embed,
|
||||
.file-block video {
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-figure {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image-figure img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.file-description {
|
||||
margin: 1rem 0 0 0;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-files {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
border: 1px solid #ffeaa7;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.tfe-footer {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: #222;
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
margin: 0;
|
||||
border-radius: 40px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
.back-button svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.footer-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.footer-meta .separator {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.tfe-header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.tfe-title {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.tfe-subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.meta-group {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.back-button svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.tfe-main {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.tfe-container {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tfe-metadata,
|
||||
.tfe-synopsis,
|
||||
.file-block {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.metadata-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tfe-footer {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.tfe-title {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.tfe-subtitle {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.meta-group {
|
||||
font-size: 0.8rem;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.back-button svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.tfe-synopsis h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.file-block embed {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.tfe-main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.tfe-container {
|
||||
gap: 1rem;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.tfe-main {
|
||||
padding: 1.5rem 1rem 2rem;
|
||||
}
|
||||
|
||||
.tfe-author {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.tfe-title {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
181
public/index.php
181
public/index.php
@@ -3,20 +3,15 @@
|
||||
require_once __DIR__ . '/../config/bootstrap.php';
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
|
||||
$pageTitle = "Liste des TFE";
|
||||
$page = isset($_GET["page"]) ? intval($_GET["page"]) : 1;
|
||||
$year = isset($_GET["year"]) ? intval($_GET["year"]) : null;
|
||||
// Default to 12 items (4 cols × 3 rows)
|
||||
$itemsPerPage = 12;
|
||||
$itemsPerPage = 24; // bigger grid
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$offset = ($page - 1) * $itemsPerPage;
|
||||
|
||||
// Get available years for footer
|
||||
$availableYears = $db->getAvailableYears();
|
||||
|
||||
// Filter by year if specified
|
||||
|
||||
if ($year) {
|
||||
$itemsToLoad = $db->searchTheses(['year' => $year], $itemsPerPage, $offset);
|
||||
$totalItems = $db->countSearchResults(['year' => $year]);
|
||||
@@ -24,91 +19,109 @@ try {
|
||||
$itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset);
|
||||
$totalItems = $db->countPublishedTheses();
|
||||
}
|
||||
|
||||
|
||||
$totalPages = ceil($totalItems / $itemsPerPage);
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading theses: " . $e->getMessage());
|
||||
$itemsToLoad = [];
|
||||
$totalPages = 0;
|
||||
$availableYears = [];
|
||||
$totalItems = 0;
|
||||
}
|
||||
|
||||
include APP_ROOT . '/templates/header.php';
|
||||
$currentNav = '';
|
||||
?>
|
||||
|
||||
<?php if ($year): ?>
|
||||
<div class="filter-info">
|
||||
Année : <?= htmlspecialchars($year) ?>
|
||||
<a href="index.php" class="clear-filter">✕ Réinitialiser</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<main>
|
||||
<div class="cards-container">
|
||||
<?php foreach ($itemsToLoad as $item): ?>
|
||||
<a href="tfe.php?id=<?= (int)$item["id"] ?>" class="card-link">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h2 class="title"><?= htmlspecialchars($item["title"]) ?></h2>
|
||||
<p class="authors"><?= htmlspecialchars($item["authors"]) ?></p>
|
||||
<p class="year"><?= htmlspecialchars($item["year"]) ?></p>
|
||||
<?php if (!empty($item["keywords"])): ?>
|
||||
<div class="tags">
|
||||
<?php
|
||||
$keywords = explode(',', $item["keywords"]);
|
||||
foreach (array_slice($keywords, 0, 3) as $keyword):
|
||||
?>
|
||||
<span class="tag"><?= htmlspecialchars(trim($keyword)) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (empty($itemsToLoad)): ?>
|
||||
<p>Aucun mémoire trouvé.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<nav class="pagination">
|
||||
<?php
|
||||
$yearParam = $year ? '&year=' . (int)$year : '';
|
||||
?>
|
||||
|
||||
<a href="?page=1<?= $yearParam ?>"
|
||||
class="pagination-btn <?= $page <= 1 ? 'disabled' : '' ?>"
|
||||
<?= $page <= 1 ? 'aria-disabled="true"' : '' ?>>
|
||||
‹‹
|
||||
</a>
|
||||
|
||||
<a href="?page=<?= max(1, (int)($page - 1)) . $yearParam ?>"
|
||||
class="pagination-btn <?= $page <= 1 ? 'disabled' : '' ?>"
|
||||
<?= $page <= 1 ? 'aria-disabled="true"' : '' ?>>
|
||||
‹
|
||||
</a>
|
||||
|
||||
<span class="pagination-info">
|
||||
<span class="page-current"><?= (int)$page ?></span>
|
||||
<span class="page-separator">/</span>
|
||||
<span class="page-total"><?= (int)$totalPages ?></span>
|
||||
</span>
|
||||
|
||||
<a href="?page=<?= min($totalPages, (int)($page + 1)) . $yearParam ?>"
|
||||
class="pagination-btn <?= $page >= $totalPages ? 'disabled' : '' ?>"
|
||||
<?= $page >= $totalPages ? 'aria-disabled="true"' : '' ?>>
|
||||
›
|
||||
</a>
|
||||
|
||||
<a href="?page=<?= $totalPages . $yearParam ?>"
|
||||
class="pagination-btn <?= $page >= $totalPages ? 'disabled' : '' ?>"
|
||||
<?= $page >= $totalPages ? 'aria-disabled="true"' : '' ?>>
|
||||
››
|
||||
</a>
|
||||
</nav>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Posterg</title>
|
||||
<link rel="stylesheet" href="assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="assets/common.css">
|
||||
<link rel="stylesheet" href="assets/main.css">
|
||||
<?php if (php_sapi_name() === 'cli-server'): ?>
|
||||
<script>
|
||||
(function poll() {
|
||||
fetch('/live-reload.php').then(r=>r.json()).then(d=>{
|
||||
if(d.changed) location.reload(); else setTimeout(poll,1000);
|
||||
}).catch(()=>setTimeout(poll,2000));
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
</head>
|
||||
<body class="home-body">
|
||||
|
||||
<?php include APP_ROOT . '/templates/footer.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/nav.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/search-bar.php'; ?>
|
||||
|
||||
<?php if ($year): ?>
|
||||
<div class="filter-info">
|
||||
Année : <?= htmlspecialchars($year) ?>
|
||||
<a href="index.php" class="clear-filter">✕ Réinitialiser</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<main class="home-main">
|
||||
<div class="cards-container">
|
||||
<?php foreach ($itemsToLoad as $item): ?>
|
||||
<a href="tfe.php?id=<?= (int)$item["id"] ?>" class="card-link">
|
||||
<div class="card">
|
||||
<div class="card__media">
|
||||
<?php
|
||||
// Use first image/video file as thumbnail
|
||||
$thumb = null;
|
||||
if (!empty($item['files'])) {
|
||||
foreach ($item['files'] as $f) {
|
||||
$ext = strtolower(pathinfo($f['file_path'], PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ['jpg','jpeg','png','gif','webp'])) {
|
||||
$thumb = $f['file_path'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also check cover image
|
||||
if (!$thumb && !empty($item['cover_image'])) {
|
||||
$thumb = $item['cover_image'];
|
||||
}
|
||||
?>
|
||||
<?php if ($thumb): ?>
|
||||
<img src="/media.php?path=<?= urlencode($thumb) ?>"
|
||||
alt="<?= htmlspecialchars($item['title']) ?>"
|
||||
loading="lazy">
|
||||
<?php else: ?>
|
||||
<div class="card__media--placeholder">◻</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card__info">
|
||||
<p class="authors"><?= htmlspecialchars($item["authors"] ?? '') ?><?php if (!empty($item['authors']) && !empty($item['title'])): ?> – <?php endif; ?><?= htmlspecialchars($item["title"]) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if (empty($itemsToLoad)): ?>
|
||||
<p style="padding:2rem;color:#666;">Aucun mémoire trouvé.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<div class="pagination-wrap">
|
||||
<?php $yearParam = $year ? '&year=' . (int)$year : ''; ?>
|
||||
<a href="?page=1<?= $yearParam ?>"
|
||||
class="pagination-btn <?= $page <= 1 ? 'disabled' : '' ?>">«</a>
|
||||
<a href="?page=<?= max(1, $page - 1) . $yearParam ?>"
|
||||
class="pagination-btn <?= $page <= 1 ? 'disabled' : '' ?>">‹</a>
|
||||
<span class="pagination-info">
|
||||
<span class="page-current"><?= $page ?></span> / <?= $totalPages ?>
|
||||
</span>
|
||||
<a href="?page=<?= min($totalPages, $page + 1) . $yearParam ?>"
|
||||
class="pagination-btn <?= $page >= $totalPages ? 'disabled' : '' ?>">›</a>
|
||||
<a href="?page=<?= $totalPages . $yearParam ?>"
|
||||
class="pagination-btn <?= $page >= $totalPages ? 'disabled' : '' ?>">»</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,356 +1,254 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . '/../config/bootstrap.php';
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
require_once APP_ROOT . '/src/RateLimit.php';
|
||||
|
||||
// Rate limiting: 30 requests per minute
|
||||
// Rate limiting
|
||||
$rateLimit = new RateLimit(30, 60);
|
||||
|
||||
// Check rate limit
|
||||
if (!$rateLimit->check()) {
|
||||
http_response_code(429);
|
||||
header('Retry-After: ' . $rateLimit->getResetTime());
|
||||
$rateLimit->sendHeaders();
|
||||
|
||||
// Simple error page
|
||||
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Rate Limit</title></head><body>';
|
||||
echo '<h1>Trop de requêtes</h1>';
|
||||
echo '<p>Vous avez dépassé la limite de 30 recherches par minute. Veuillez réessayer dans ' . $rateLimit->getResetTime() . ' secondes.</p>';
|
||||
echo '</body></html>';
|
||||
echo '<!DOCTYPE html><html><body><h1>Trop de requêtes</h1><p>Réessayez dans ' . $rateLimit->getResetTime() . ' secondes.</p></body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
$rateLimit->sendHeaders();
|
||||
if (rand(1, 100) === 1) $rateLimit->cleanup();
|
||||
|
||||
if (rand(1, 100) === 1) {
|
||||
$rateLimit->cleanup();
|
||||
}
|
||||
|
||||
// Pagination - adjust to grid
|
||||
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
|
||||
$itemsPerPage = 9; // Default grid size (3 rows × 3 columns)
|
||||
|
||||
// Collect search parameters
|
||||
// Collect search/filter params
|
||||
$searchParams = [];
|
||||
if (!empty($_GET['query'])) {
|
||||
$searchParams['query'] = trim($_GET['query']);
|
||||
}
|
||||
if (!empty($_GET['year'])) {
|
||||
$searchParams['year'] = intval($_GET['year']);
|
||||
}
|
||||
if (!empty($_GET['orientation'])) {
|
||||
$searchParams['orientation'] = $_GET['orientation'];
|
||||
}
|
||||
if (!empty($_GET['ap_program'])) {
|
||||
$searchParams['ap_program'] = $_GET['ap_program'];
|
||||
}
|
||||
if (!empty($_GET['finality'])) {
|
||||
$searchParams['finality'] = $_GET['finality'];
|
||||
}
|
||||
if (!empty($_GET['keyword'])) {
|
||||
$searchParams['keyword'] = $_GET['keyword'];
|
||||
}
|
||||
if (!empty($_GET['format'])) {
|
||||
$searchParams['format'] = $_GET['format'];
|
||||
}
|
||||
if (!empty($_GET['language'])) {
|
||||
$searchParams['language'] = $_GET['language'];
|
||||
}
|
||||
if (isset($_GET['is_doctoral'])) {
|
||||
$searchParams['is_doctoral'] = $_GET['is_doctoral'] === '1';
|
||||
}
|
||||
if (!empty($_GET['query'])) $searchParams['query'] = trim($_GET['query']);
|
||||
if (!empty($_GET['year'])) $searchParams['year'] = intval($_GET['year']);
|
||||
if (!empty($_GET['orientation'])) $searchParams['orientation'] = $_GET['orientation'];
|
||||
if (!empty($_GET['ap_program'])) $searchParams['ap_program'] = $_GET['ap_program'];
|
||||
if (!empty($_GET['keyword'])) $searchParams['keyword'] = $_GET['keyword'];
|
||||
|
||||
$hasSearch = !empty($searchParams);
|
||||
|
||||
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
|
||||
$itemsPerPage = 30;
|
||||
$validationError = null;
|
||||
$showFilters = isset($_GET['filters']) && $_GET['filters'] === 'show';
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Get search results
|
||||
$offset = ($page - 1) * $itemsPerPage;
|
||||
$results = $db->searchTheses($searchParams, $itemsPerPage, $offset);
|
||||
$totalItems = $db->countSearchResults($searchParams);
|
||||
$totalPages = ceil($totalItems / $itemsPerPage);
|
||||
|
||||
// Get filter options
|
||||
$years = $db->getAvailableYears();
|
||||
if ($hasSearch) {
|
||||
$results = $db->searchTheses($searchParams, $itemsPerPage, $offset);
|
||||
$totalItems = $db->countSearchResults($searchParams);
|
||||
$totalPages = ceil($totalItems / $itemsPerPage);
|
||||
} else {
|
||||
$results = [];
|
||||
$totalItems = 0;
|
||||
$totalPages = 0;
|
||||
}
|
||||
|
||||
$years = $db->getAvailableYears();
|
||||
$orientations = $db->getOrientations();
|
||||
$apPrograms = $db->getApPrograms();
|
||||
$finalityTypes = $db->getFinalityTypes();
|
||||
$keywords = $db->getUsedKeywords();
|
||||
$formats = $db->getFormatTypes();
|
||||
$languages = $db->getLanguages();
|
||||
|
||||
$apPrograms = $db->getApPrograms();
|
||||
$keywords = $db->getUsedKeywords();
|
||||
// Get all published theses for student index (multiple pages if needed)
|
||||
$students = $db->searchTheses([], 100, 0); // max 100 per DB limit
|
||||
} catch (InvalidArgumentException $e) {
|
||||
error_log("Search validation error: " . $e->getMessage());
|
||||
$validationError = $e->getMessage();
|
||||
$results = [];
|
||||
$totalPages = 0;
|
||||
$totalItems = 0;
|
||||
$years = [];
|
||||
$orientations = [];
|
||||
$apPrograms = [];
|
||||
$finalityTypes = [];
|
||||
$keywords = [];
|
||||
$formats = [];
|
||||
$languages = [];
|
||||
$results = []; $totalItems = 0; $totalPages = 0;
|
||||
$years = []; $orientations = []; $apPrograms = []; $keywords = []; $students = [];
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in search: " . $e->getMessage());
|
||||
$validationError = "Une erreur est survenue lors de la recherche.";
|
||||
$results = [];
|
||||
$totalPages = 0;
|
||||
$totalItems = 0;
|
||||
$years = [];
|
||||
$orientations = [];
|
||||
$apPrograms = [];
|
||||
$finalityTypes = [];
|
||||
$keywords = [];
|
||||
$formats = [];
|
||||
$languages = [];
|
||||
error_log("Search error: " . $e->getMessage());
|
||||
$validationError = "Une erreur est survenue.";
|
||||
$results = []; $totalItems = 0; $totalPages = 0;
|
||||
$years = []; $orientations = []; $apPrograms = []; $keywords = []; $students = [];
|
||||
}
|
||||
|
||||
$currentNav = 'repertoire';
|
||||
$searchBarValue = $_GET['query'] ?? '';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Recherche - Posterg</title>
|
||||
<title>Répertoire – Posterg</title>
|
||||
<link rel="stylesheet" href="assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="assets/common.css">
|
||||
<link rel="stylesheet" href="assets/search.css">
|
||||
<?php if (php_sapi_name() === 'cli-server'): ?>
|
||||
<script>
|
||||
(function poll() {
|
||||
fetch('/live-reload.php').then(r=>r.json()).then(d=>{
|
||||
if(d.changed) location.reload(); else setTimeout(poll,1000);
|
||||
}).catch(()=>setTimeout(poll,2000));
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="search-header">
|
||||
<a href="index.php" class="back-button">← Retour</a>
|
||||
|
||||
<form method="GET" action="search.php" class="search-form">
|
||||
<input
|
||||
type="text"
|
||||
name="query"
|
||||
class="search-input"
|
||||
placeholder="Rechercher..."
|
||||
value="<?= htmlspecialchars($_GET['query'] ?? ''); ?>"
|
||||
autofocus
|
||||
>
|
||||
|
||||
<div class="search-actions">
|
||||
<button type="submit" class="search-button">Rechercher</button>
|
||||
<button type="button" class="filter-button <?= $showFilters ? 'active' : ''; ?>" onclick="toggleFilters()">
|
||||
Filtres
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Hidden field to maintain filter panel state -->
|
||||
<input type="hidden" name="filters" id="filters-state" value="<?= $showFilters ? 'show' : 'hide'; ?>">
|
||||
|
||||
<!-- Preserve other filter values as hidden fields when searching -->
|
||||
<?php foreach (['year', 'orientation', 'ap_program', 'finality', 'keyword', 'format', 'language', 'is_doctoral'] as $field): ?>
|
||||
<?php if (!empty($_GET[$field])): ?>
|
||||
<input type="hidden" name="<?= $field; ?>" value="<?= htmlspecialchars($_GET[$field]); ?>">
|
||||
<?php endif; ?>
|
||||
<body class="search-body">
|
||||
|
||||
<?php include APP_ROOT . '/templates/nav.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/search-bar.php'; ?>
|
||||
|
||||
<?php if ($validationError): ?>
|
||||
<div class="search-error">⚠ <?= htmlspecialchars($validationError) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($hasSearch): ?>
|
||||
<!-- ── RESULTS VIEW ─────────────────────────────────── -->
|
||||
|
||||
<!-- Filter controls -->
|
||||
<form class="search-controls" method="GET" action="search.php">
|
||||
<input type="hidden" name="query" value="<?= htmlspecialchars($_GET['query'] ?? '') ?>">
|
||||
|
||||
<div class="search-filter-group">
|
||||
<span class="search-filter-label">Année</span>
|
||||
<select class="search-filter-select" name="year">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($years as $y): ?>
|
||||
<option value="<?= (int)$y ?>" <?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'selected' : '' ?>>
|
||||
<?= (int)$y ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</form>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<?php if ($validationError): ?>
|
||||
<div class="error-message">
|
||||
⚠ <?= htmlspecialchars($validationError); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="filters-panel <?= $showFilters ? 'show' : ''; ?>" id="filters-panel">
|
||||
<form method="GET" action="search.php">
|
||||
<!-- Preserve query when using filters -->
|
||||
<?php if (!empty($_GET['query'])): ?>
|
||||
<input type="hidden" name="query" value="<?= htmlspecialchars($_GET['query']); ?>">
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="filters" value="show">
|
||||
|
||||
<div class="filters-grid">
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Année</label>
|
||||
<select name="year" class="filter-select">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($years as $year): ?>
|
||||
<option value="<?= (int)$year; ?>" <?= (isset($_GET['year']) && $_GET['year'] == $year) ? 'selected' : ''; ?>>
|
||||
<?= (int)$year; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Orientation</label>
|
||||
<select name="orientation" class="filter-select">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($orientations as $orientation): ?>
|
||||
<option value="<?= htmlspecialchars($orientation['name']); ?>"
|
||||
<?= (isset($_GET['orientation']) && $_GET['orientation'] == $orientation['name']) ? 'selected' : ''; ?>>
|
||||
<?= htmlspecialchars($orientation['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">AP</label>
|
||||
<select name="ap_program" class="filter-select">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?= htmlspecialchars($ap['name']); ?>"
|
||||
<?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'selected' : ''; ?>>
|
||||
<?= htmlspecialchars($ap['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Finalité</label>
|
||||
<select name="finality" class="filter-select">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($finalityTypes as $finality): ?>
|
||||
<option value="<?= htmlspecialchars($finality['name']); ?>"
|
||||
<?= (isset($_GET['finality']) && $_GET['finality'] == $finality['name']) ? 'selected' : ''; ?>>
|
||||
<?= htmlspecialchars($finality['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Format</label>
|
||||
<select name="format" class="filter-select">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($formats as $format): ?>
|
||||
<option value="<?= htmlspecialchars($format['name']); ?>"
|
||||
<?= (isset($_GET['format']) && $_GET['format'] == $format['name']) ? 'selected' : ''; ?>>
|
||||
<?= htmlspecialchars($format['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Langue</label>
|
||||
<select name="language" class="filter-select">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($languages as $language): ?>
|
||||
<option value="<?= htmlspecialchars($language['name']); ?>"
|
||||
<?= (isset($_GET['language']) && $_GET['language'] == $language['name']) ? 'selected' : ''; ?>>
|
||||
<?= htmlspecialchars($language['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Mot-clé</label>
|
||||
<select name="keyword" class="filter-select">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($keywords as $keyword): ?>
|
||||
<option value="<?= htmlspecialchars($keyword['keyword']); ?>"
|
||||
<?= (isset($_GET['keyword']) && $_GET['keyword'] == $keyword['keyword']) ? 'selected' : ''; ?>>
|
||||
<?= htmlspecialchars($keyword['keyword']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">Type</label>
|
||||
<select name="is_doctoral" class="filter-select">
|
||||
<option value="">Tous</option>
|
||||
<option value="0" <?= (isset($_GET['is_doctoral']) && $_GET['is_doctoral'] == '0') ? 'selected' : ''; ?>>
|
||||
TFE uniquement
|
||||
</option>
|
||||
<option value="1" <?= (isset($_GET['is_doctoral']) && $_GET['is_doctoral'] == '1') ? 'selected' : ''; ?>>
|
||||
Thèses doctorales
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-actions">
|
||||
<button type="submit" class="search-button">Appliquer</button>
|
||||
<a href="search.php?filters=show" class="reset-button">Réinitialiser</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="search-filter-group">
|
||||
<span class="search-filter-label">Orientation</span>
|
||||
<select class="search-filter-select" name="orientation">
|
||||
<option value="">Toutes</option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= htmlspecialchars($o['name']) ?>"
|
||||
<?= (isset($_GET['orientation']) && $_GET['orientation'] == $o['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<main>
|
||||
<div class="cards-container">
|
||||
<?php if (count($results) > 0): ?>
|
||||
|
||||
<div class="search-filter-group">
|
||||
<span class="search-filter-label">AP</span>
|
||||
<select class="search-filter-select" name="ap_program">
|
||||
<option value="">Tous</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<option value="<?= htmlspecialchars($ap['name']) ?>"
|
||||
<?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($ap['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="search-apply-btn">Filtrer</button>
|
||||
<a href="search.php" class="search-reset-link">Réinitialiser</a>
|
||||
</form>
|
||||
|
||||
<main class="search-main">
|
||||
<div class="search-results-view">
|
||||
<p class="search-results-header"><?= $totalItems ?> résultat<?= $totalItems > 1 ? 's' : '' ?></p>
|
||||
|
||||
<?php if (!empty($results)): ?>
|
||||
<div class="results-grid">
|
||||
<?php foreach ($results as $item): ?>
|
||||
<a href="tfe.php?id=<?= (int)$item['id']; ?>" class="card-link">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="title"><?= htmlspecialchars($item['title']); ?></h3>
|
||||
<p class="authors"><?= htmlspecialchars($item['authors'] ?? 'Auteur inconnu'); ?></p>
|
||||
<p class="year"><?= htmlspecialchars($item['year']); ?></p>
|
||||
<?php if (!empty($item['orientation'])): ?>
|
||||
<div class="tags">
|
||||
<span class="tag"><?= htmlspecialchars($item['orientation']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="tfe.php?id=<?= (int)$item['id'] ?>" class="result-card">
|
||||
<span class="result-card__authors"><?= htmlspecialchars($item['authors'] ?? '') ?></span>
|
||||
<span class="result-card__title"><?= htmlspecialchars($item['title']) ?></span>
|
||||
<span class="result-card__meta"><?= htmlspecialchars($item['year']) ?><?php if (!empty($item['orientation'])): ?> · <?= htmlspecialchars($item['orientation']) ?><?php endif; ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<?php elseif (!empty($searchParams)): ?>
|
||||
<div style="grid-column: 1 / -1; display: flex; align-items: center; justify-content: center; color: white; font-size: 1.2rem;">
|
||||
Aucun résultat trouvé pour cette recherche.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<div style="display:flex;gap:.5rem;justify-content:center;align-items:center;padding:1.5rem 0;">
|
||||
<a href="?<?= http_build_query(array_merge($_GET, ['page' => max(1, $page - 1)])) ?>"
|
||||
style="padding:.25rem .7rem;border:1px solid #ddd;border-radius:3px;color:#111;text-decoration:none;<?= $page <= 1 ? 'opacity:.3;pointer-events:none;' : '' ?>">‹</a>
|
||||
<span style="font-size:.9rem;color:#666;"><?= $page ?> / <?= $totalPages ?></span>
|
||||
<a href="?<?= http_build_query(array_merge($_GET, ['page' => min($totalPages, $page + 1)])) ?>"
|
||||
style="padding:.25rem .7rem;border:1px solid #ddd;border-radius:3px;color:#111;text-decoration:none;<?= $page >= $totalPages ? 'opacity:.3;pointer-events:none;' : '' ?>">›</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php else: ?>
|
||||
<div style="grid-column: 1 / -1; display: flex; align-items: center; justify-content: center; color: white; font-size: 1.2rem;">
|
||||
Utilisez la barre de recherche pour trouver des mémoires.
|
||||
</div>
|
||||
<p class="search-empty">Aucun résultat pour cette recherche.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<div class="pagination">
|
||||
<a href="?<?= http_build_query(array_merge($_GET, ['page' => max(1, $page - 1)])); ?>"
|
||||
class="pagination-btn <?= $page <= 1 ? 'disabled' : ''; ?>">
|
||||
←
|
||||
</a>
|
||||
<div class="pagination-info">
|
||||
<span class="page-current"><?= $page; ?></span>
|
||||
<span class="page-separator">/</span>
|
||||
<span class="page-total"><?= $totalPages; ?></span>
|
||||
</div>
|
||||
<a href="?<?= http_build_query(array_merge($_GET, ['page' => min($totalPages, $page + 1)])); ?>"
|
||||
class="pagination-btn <?= $page >= $totalPages ? 'disabled' : ''; ?>">
|
||||
→
|
||||
</a>
|
||||
</main>
|
||||
|
||||
<?php else: ?>
|
||||
<!-- ── RÉPERTOIRE INDEX VIEW ─────────────────────────── -->
|
||||
<main class="search-main">
|
||||
<div class="repertoire-index">
|
||||
|
||||
<!-- ANNÉES -->
|
||||
<div class="repertoire-col">
|
||||
<h2 class="repertoire-col__header">Années</h2>
|
||||
<?php foreach ($years as $y): ?>
|
||||
<a href="search.php?year=<?= (int)$y ?>"
|
||||
class="year-index-item <?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'active' : '' ?>">
|
||||
<?= (int)$y ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="results-footer">
|
||||
<span class="results-count"><?= (int)$totalItems; ?></span>
|
||||
<span>résultat<?= $totalItems > 1 ? 's' : ''; ?></span>
|
||||
|
||||
<!-- CATÉGORIES -->
|
||||
<div class="repertoire-col">
|
||||
<h2 class="repertoire-col__header">Catégories</h2>
|
||||
|
||||
<?php if (!empty($orientations)): ?>
|
||||
<span class="cat-index-label">Orientation</span>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<a href="search.php?orientation=<?= urlencode($o['name']) ?>"
|
||||
class="cat-index-item <?= (isset($_GET['orientation']) && $_GET['orientation'] == $o['name']) ? 'active' : '' ?>">
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($apPrograms)): ?>
|
||||
<span class="cat-index-label">Ateliers Pluridisciplinaires</span>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
<a href="search.php?ap_program=<?= urlencode($ap['name']) ?>"
|
||||
class="cat-index-item <?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'active' : '' ?>">
|
||||
<?= htmlspecialchars($ap['name']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleFilters() {
|
||||
const panel = document.getElementById('filters-panel');
|
||||
const button = document.querySelector('.filter-button');
|
||||
const state = document.getElementById('filters-state');
|
||||
|
||||
panel.classList.toggle('show');
|
||||
button.classList.toggle('active');
|
||||
|
||||
// Update hidden field
|
||||
state.value = panel.classList.contains('show') ? 'show' : 'hide';
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- ÉTUDIANTES -->
|
||||
<div class="repertoire-col">
|
||||
<h2 class="repertoire-col__header">Étudiantes</h2>
|
||||
<?php
|
||||
// Build unique author → thesis list
|
||||
$authorMap = [];
|
||||
foreach ($students as $s) {
|
||||
if (empty($s['authors'])) continue;
|
||||
$names = explode(',', $s['authors']);
|
||||
foreach ($names as $name) {
|
||||
$name = trim($name);
|
||||
if ($name && !isset($authorMap[$name])) {
|
||||
$authorMap[$name] = $s['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
ksort($authorMap);
|
||||
foreach ($authorMap as $name => $id): ?>
|
||||
<a href="tfe.php?id=<?= (int)$id ?>" class="student-index-item">
|
||||
<?= htmlspecialchars($name) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- MOTS-CLÉS -->
|
||||
<div class="repertoire-col">
|
||||
<h2 class="repertoire-col__header">Mots-clés</h2>
|
||||
<?php foreach ($keywords as $kw): ?>
|
||||
<a href="search.php?keyword=<?= urlencode($kw['keyword']) ?>"
|
||||
class="keyword-index-item <?= (isset($_GET['keyword']) && $_GET['keyword'] == $kw['keyword']) ? 'active' : '' ?>">
|
||||
<?= htmlspecialchars($kw['keyword']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
<?php endif; ?>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
276
public/tfe.php
276
public/tfe.php
@@ -1,202 +1,172 @@
|
||||
<?php
|
||||
// Bootstrap application
|
||||
require_once __DIR__ . '/../config/bootstrap.php';
|
||||
|
||||
// Load required libraries and classes
|
||||
require_once APP_ROOT . '/src/Database.php';
|
||||
|
||||
// Check if an id parameter is provided in the URL
|
||||
if (isset($_GET['id'])) {
|
||||
$thesisId = intval($_GET['id']);
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$data = $db->getThesisById($thesisId);
|
||||
|
||||
if (!$data) {
|
||||
// Thesis not found or not published
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
if (!$data) { header('Location: index.php'); exit; }
|
||||
} catch (Exception $e) {
|
||||
error_log("Error loading thesis: " . $e->getMessage());
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
header('Location: index.php'); exit;
|
||||
}
|
||||
} else {
|
||||
// Redirect to the index page if no id parameter is provided
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
header('Location: index.php'); exit;
|
||||
}
|
||||
|
||||
// Set page title and additional CSS
|
||||
$pageTitle = $data['title'];
|
||||
$additionalCSS = ['assets/tfe.css'];
|
||||
|
||||
// Include shared head template
|
||||
include APP_ROOT . '/templates/head.php';
|
||||
$currentNav = '';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= htmlspecialchars($data['title']) ?> – Posterg</title>
|
||||
<link rel="stylesheet" href="assets/modern-normalize.min.css">
|
||||
<link rel="stylesheet" href="assets/common.css">
|
||||
<link rel="stylesheet" href="assets/tfe.css">
|
||||
<?php if (php_sapi_name() === 'cli-server'): ?>
|
||||
<script>
|
||||
(function poll(){
|
||||
fetch('/live-reload.php').then(r=>r.json()).then(d=>{
|
||||
if(d.changed) location.reload(); else setTimeout(poll,1000);
|
||||
}).catch(()=>setTimeout(poll,2000));
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
<body class="tfe-body">
|
||||
<header class="tfe-header">
|
||||
<div class="header-content">
|
||||
<h1 class="tfe-title"><?= htmlspecialchars($data['title']); ?></h1>
|
||||
<?php if (!empty($data['subtitle'])): ?>
|
||||
<p class="tfe-subtitle"><?= htmlspecialchars($data['subtitle']); ?></p>
|
||||
<?php endif; ?>
|
||||
<div class="header-metadata">
|
||||
<div class="meta-group">
|
||||
<span class="author"><?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu'); ?></span>
|
||||
<span class="separator">•</span>
|
||||
<span class="year"><?= htmlspecialchars($data['year']); ?></span>
|
||||
</div>
|
||||
<?php if (!empty($data['orientation']) || !empty($data['ap_program'])): ?>
|
||||
<div class="meta-group">
|
||||
<?php if (!empty($data['orientation'])): ?>
|
||||
<span><?= htmlspecialchars($data['orientation']); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['orientation']) && !empty($data['ap_program'])): ?>
|
||||
<span class="separator">•</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['ap_program'])): ?>
|
||||
<span><?= htmlspecialchars($data['ap_program']); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['finality_type'])): ?>
|
||||
<div class="meta-group">
|
||||
<span><?= htmlspecialchars($data['finality_type']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['keywords'])): ?>
|
||||
<div class="meta-group keywords">
|
||||
<span class="label">Mots-clés:</span>
|
||||
<span><?= htmlspecialchars($data['keywords']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<?php include APP_ROOT . '/templates/nav.php'; ?>
|
||||
<?php include APP_ROOT . '/templates/search-bar.php'; ?>
|
||||
|
||||
<main class="tfe-main">
|
||||
<div class="tfe-container">
|
||||
|
||||
<!-- Metadata Section -->
|
||||
<section class="tfe-metadata">
|
||||
<?php if (!empty($data['subtitle'])): ?>
|
||||
<h2 class="subtitle"><?= htmlspecialchars($data['subtitle']); ?></h2>
|
||||
<?php endif; ?>
|
||||
<div class="tfe-layout">
|
||||
|
||||
<div class="metadata-grid">
|
||||
<div class="metadata-column">
|
||||
<?php if (!empty($data['orientation']) || !empty($data['ap_program'])): ?>
|
||||
<div class="meta-item">
|
||||
<strong>Programme:</strong>
|
||||
<span>
|
||||
<?php if (!empty($data['orientation'])): ?>
|
||||
<?= htmlspecialchars($data['orientation']); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['orientation']) && !empty($data['ap_program'])): ?>
|
||||
et
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['ap_program'])): ?>
|
||||
<?= htmlspecialchars($data['ap_program']); ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<!-- LEFT: info -->
|
||||
<div class="tfe-left">
|
||||
<h1 class="tfe-author"><?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu') ?></h1>
|
||||
|
||||
<?php if (!empty($data['finality_type'])): ?>
|
||||
<div class="meta-item">
|
||||
<strong>Finalité:</strong>
|
||||
<span><?= htmlspecialchars($data['finality_type']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<h2 class="tfe-title">
|
||||
<?= htmlspecialchars($data['title']) ?>
|
||||
<?php if (!empty($data['subtitle'])): ?>
|
||||
– <?= htmlspecialchars($data['subtitle']) ?>
|
||||
<?php endif; ?>
|
||||
</h2>
|
||||
|
||||
<?php if (!empty($data['supervisors'])): ?>
|
||||
<div class="meta-item">
|
||||
<strong>Promoteur·ice·s:</strong>
|
||||
<span><?= htmlspecialchars($data['supervisors']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="tfe-meta-list">
|
||||
<?php if (!empty($data['orientation'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Orientation :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['orientation']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="metadata-column">
|
||||
<?php if (!empty($data['languages'])): ?>
|
||||
<div class="meta-item">
|
||||
<strong>Langue(s):</strong>
|
||||
<span><?= htmlspecialchars($data['languages']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['formats'])): ?>
|
||||
<div class="meta-item">
|
||||
<strong>Format(s):</strong>
|
||||
<span><?= htmlspecialchars($data['formats']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['ap_program'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Atelier pluridisciplinaire :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['ap_program']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['year'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Date :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['year']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['languages'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Langue :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['languages']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['formats'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Format :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['formats']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['file_size_info'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Durée :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['file_size_info']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['keywords'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Mots-clés :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['keywords']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['supervisors'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Promoteur·ice interne :</span>
|
||||
<span class="value"><?= htmlspecialchars($data['supervisors']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['baiu_link'])): ?>
|
||||
<div class="tfe-meta-item">
|
||||
<span class="label">Contact :</span>
|
||||
<span class="value">
|
||||
<a href="<?= htmlspecialchars($data['baiu_link']) ?>" target="_blank" rel="noopener">
|
||||
<?= htmlspecialchars($data['baiu_link']) ?>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($data['context_note'])): ?>
|
||||
<div class="context-note">
|
||||
<em><?= htmlspecialchars($data['context_note']); ?></em>
|
||||
</div>
|
||||
<?php if (!empty($data['synopsis'])): ?>
|
||||
<div class="tfe-synopsis-text">
|
||||
<?= nl2br(htmlspecialchars($data['synopsis'])) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- Synopsis Section -->
|
||||
<?php if (!empty($data['synopsis'])): ?>
|
||||
<section class="tfe-synopsis">
|
||||
<h3>Synopsis</h3>
|
||||
<div class="synopsis-content">
|
||||
<?= nl2br(htmlspecialchars($data['synopsis'])); ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<div style="margin-top:1.5rem;">
|
||||
<a href="index.php" style="font-size:.88rem;color:#666;text-decoration:underline;text-underline-offset:2px;">
|
||||
← Retour
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Files Section -->
|
||||
<section class="tfe-files">
|
||||
<?php if (isset($data['files']) && count($data['files']) > 0): ?>
|
||||
<!-- RIGHT: media -->
|
||||
<div class="tfe-right">
|
||||
<?php if (!empty($data['files'])): ?>
|
||||
<?php foreach ($data['files'] as $file): ?>
|
||||
<?php $ext = strtolower(pathinfo($file['file_path'], PATHINFO_EXTENSION)); ?>
|
||||
<div class="file-block">
|
||||
<div class="tfe-media-block">
|
||||
<?php if ($ext === 'pdf'): ?>
|
||||
<embed src="/media.php?path=<?= urlencode($file['file_path']); ?>" type="application/pdf" width="100%" height="800px" />
|
||||
<?php elseif (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'])): ?>
|
||||
<figure class="image-figure">
|
||||
<img src="/media.php?path=<?= urlencode($file['file_path']); ?>" alt="<?= htmlspecialchars($file['file_name']); ?>">
|
||||
</figure>
|
||||
<embed src="/media.php?path=<?= urlencode($file['file_path']) ?>"
|
||||
type="application/pdf" width="100%" height="700px">
|
||||
<?php elseif (in_array($ext, ['jpg','jpeg','png','gif','bmp','webp'])): ?>
|
||||
<img src="/media.php?path=<?= urlencode($file['file_path']) ?>"
|
||||
alt="<?= htmlspecialchars($file['file_name']) ?>">
|
||||
<?php elseif ($ext === 'mp4'): ?>
|
||||
<video width="100%" controls>
|
||||
<source src="/media.php?path=<?= urlencode($file['file_path']); ?>" type="video/mp4">
|
||||
Votre navigateur ne supporte pas la lecture de vidéos.
|
||||
<source src="/media.php?path=<?= urlencode($file['file_path']) ?>" type="video/mp4">
|
||||
</video>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($file['description'])): ?>
|
||||
<p class="file-description"><?= htmlspecialchars($file['description']); ?></p>
|
||||
<p class="tfe-file-caption"><?= htmlspecialchars($file['description']) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<p class="no-files">Aucun fichier disponible pour ce TFE.</p>
|
||||
<p class="tfe-no-files">Aucun fichier disponible pour ce TFE.</p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="tfe-footer">
|
||||
<div class="footer-content">
|
||||
<a href="index.php" class="back-button" aria-label="Retour à l'accueil" title="Retour à l'accueil">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="footer-meta">
|
||||
<span><?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu'); ?></span>
|
||||
<span class="separator">•</span>
|
||||
<span><?= htmlspecialchars($data['year']); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user