Files
xamxam/public/admin/student-access.php

245 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../../config/bootstrap.php';
require_once __DIR__ . '/../../src/AdminAuth.php';
require_once __DIR__ . '/../../src/ShareLink.php';
App::adminGuard();
require_once __DIR__ . '/../../src/ShareLink.php';
$shareLink = ShareLink::make();
$links = $shareLink->listAll();
$flash = App::consumeFlash();
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$baseUrl = $protocol . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost');
$pageTitle = 'Accès étudiant·e';
$isAdmin = true;
$bodyClass = 'admin-body';
?>
<?php require_once APP_ROOT . '/templates/head.php'; ?>
<?php include APP_ROOT . '/templates/header.php'; ?>
<style>
.access-main { padding: 2rem; max-width: 960px; }
.access-toolbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.5rem; }
.access-toolbar h1 { margin: 0; font-size: 1.5rem; }
.access-btn { display: inline-flex; align-items: center; gap: .4rem; padding: .5rem 1rem; border: none; border-radius: 6px; cursor: pointer; font-size: .875rem; background: #2563eb; color: #fff; text-decoration: none; }
.access-btn:hover { background: #1d4ed8; }
.access-btn--secondary { background: #f1f5f9; color: #334155; border: 1px solid #cbd5e1; }
.access-btn--secondary:hover { background: #e2e8f0; }
.access-btn--danger { background: #fee2e2; color: #b91c1c; }
.access-btn--danger:hover { background: #fca5a5; }
.access-btn--sm { padding: .3rem .6rem; font-size: .8rem; }
.access-table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
.access-table th, .access-table td { padding: .6rem .8rem; text-align: left; border-bottom: 1px solid #e2e8f0; vertical-align: middle; }
.access-table th { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: #64748b; background: #f8fafc; }
.access-table th:first-child { border-radius: 6px 0 0 0; }
.access-table th:last-child { border-radius: 0 6px 0 0; }
.access-table tbody tr:hover { background: #f8fafc; }
.access-slug { font-family: ui-monospace, SFMono-Regular, monospace; font-size: .85rem; }
.access-url-input { display: none; margin-top: .25rem; padding: .35rem .5rem; font-size: .8rem; border: 1px solid #cbd5e1; border-radius: 4px; width: 100%; max-width: 320px; font-family: ui-monospace, SFMono-Regular, monospace; }
.access-url-input.visible { display: inline-block; }
.access-badge { display: inline-block; padding: .15rem .5rem; border-radius: 9999px; font-size: .75rem; font-weight: 500; background: #dcfce7; color: #166534; }
.access-badge--disabled { background: #fee2e2; color: #b91c1c; }
.access-badge--expired { background: #fef3c7; color: #92400e; }
.access-act { display: flex; gap: .35rem; flex-wrap: wrap; }
/* ── Create dialog ── */
.access-dialog { border: none; border-radius: 12px; padding: 0; width: min(480px, 92vw); box-shadow: 0 20px 60px rgba(0,0,0,.18); max-height: 80vh; overflow: auto; }
.access-dialog::backdrop { background: rgba(0,0,0,.35); }
.access-dialog__header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.5rem; border-bottom: 1px solid #e2e8f0; }
.access-dialog__header h2 { margin: 0; font-size: 1.15rem; }
.access-dialog__close { background: none; border: none; font-size: 1.4rem; cursor: pointer; color: #64748b; line-height: 1; }
.access-dialog__body { padding: 1.5rem; }
.access-dialog__body label { display: block; margin-bottom: 1rem; font-size: .9rem; }
.access-dialog__body label input[type=text],
.access-dialog__body label input[type=password],
.access-dialog__body label input[type=datetime-local] { display: block; width: 100%; margin-top: .3rem; padding: .45rem .6rem; border: 1px solid #cbd5e1; border-radius: 6px; font-size: .9rem; box-sizing: border-box; }
.access-dialog__body small { display: block; margin-top: .25rem; color: #64748b; }
.access-dialog__footer { display: flex; justify-content: flex-end; gap: .5rem; padding: 1rem 1.5rem; border-top: 1px solid #e2e8f0; }
.access-empty { text-align: center; padding: 3rem 1rem; color: #94a3b8; }
.access-empty svg { width: 48px; height: 48px; margin-bottom: .75rem; opacity: .5; }
</style>
<main id="main-content" class="access-main">
<?php if ($flash['success']): ?>
<div class="flash flash--success"><?= htmlspecialchars($flash['success']) ?></div>
<?php endif; ?>
<?php if ($flash['error']): ?>
<div class="flash flash--error"><?= htmlspecialchars($flash['error']) ?></div>
<?php endif; ?>
<div class="access-toolbar">
<h1>Accès étudiant·e</h1>
<button type="button" class="access-btn" id="open-create-dialog">
Créer un lien
</button>
</div>
<?php if (empty($links)): ?>
<div class="access-empty">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"/></svg>
<p>Aucun lien d'accès créé.</p>
<p>Cliquez sur « Créer un lien » pour générer un lien partageable.</p>
</div>
<?php else: ?>
<table class="access-table">
<thead>
<tr>
<th scope="col">Lien</th>
<th scope="col">Statut</th>
<th scope="col">Mot de passe</th>
<th scope="col">Utilisations</th>
<th scope="col">Expiration</th>
<th scope="col">Créé le</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($links as $link): ?>
<?php
$isExpired = $link['expires_at'] !== null && strtotime($link['expires_at']) < time();
$isActive = (bool)$link['is_active'] && !$isExpired;
$statusLabel = $isExpired ? 'Expiré' : ($link['is_active'] ? 'Actif' : 'Désactivé');
$statusClass = $isExpired ? 'access-badge--expired' : ($link['is_active'] ? '' : 'access-badge--disabled');
$fullUrl = $baseUrl . '/partage/' . htmlspecialchars($link['slug']);
$created = date('d/m/Y H:i', strtotime($link['created_at']));
$expires = $link['expires_at'] ? date('d/m/Y', strtotime($link['expires_at'])) : '—';
$hasPassword = !empty($link['password_hash']);
?>
<tr>
<td>
<span class="access-slug"><?= htmlspecialchars($link['slug']) ?></span>
<input class="access-url-input" id="url-<?= $link['id'] ?>" value="<?= $fullUrl ?>" readonly>
</td>
<td><span class="access-badge <?= $statusClass ?>"><?= $statusLabel ?></span></td>
<td><?= $hasPassword ? '🔒 Oui' : 'Non' ?></td>
<td><?= intval($link['usage_count']) ?></td>
<td><?= $expires ?></td>
<td><?= $created ?></td>
<td>
<div class="access-act">
<button type="button" class="access-btn access-btn--secondary access-btn--sm"
onclick="copyUrl(<?= $link['id'] ?>)" title="Copier l'URL">
Copier
</button>
<form method="post" action="actions/student-access.php" style="display:inline;">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="toggle">
<input type="hidden" name="id" value="<?= $link['id'] ?>">
<button type="submit" class="access-btn access-btn--secondary access-btn--sm"
title="<?= $link['is_active'] ? 'Désactiver' : 'Activer' ?>">
<?= $link['is_active'] ? '⏸' : '▶' ?>
</button>
</form>
<button type="button" class="access-btn access-btn--secondary access-btn--sm"
onclick="openPasswordDialog(<?= $link['id'] ?>, <?= $hasPassword ? 'true' : 'false' ?>)"
title="Modifier le mot de passe">
🔑
</button>
<form method="post" action="actions/student-access.php" style="display:inline;"
onsubmit="return confirm('Supprimer ce lien ? Les soumissions via ce lien seront bloquées.')">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $link['id'] ?>">
<button type="submit" class="access-btn access-btn--danger access-btn--sm" title="Supprimer">
🗑
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</main>
<!-- ═══════════════════════ CREATE DIALOG ═══════════════════════ -->
<dialog id="create-dialog" class="access-dialog">
<div class="access-dialog__header">
<h2>Créer un lien d'accès</h2>
<button type="button" class="access-dialog__close" onclick="document.getElementById('create-dialog').close()">&#x2715;</button>
</div>
<form method="post" action="actions/student-access.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="create">
<div class="access-dialog__body">
<label>
Mot de passe (optionnel)
<input type="password" name="password" autocomplete="new-password">
<small>Laissez vide pour un lien sans mot de passe.</small>
</label>
<label>
Expiration (optionnel)
<input type="datetime-local" name="expires_at">
<small>Laissez vide pour qu'il n'expire jamais.</small>
</label>
</div>
<div class="access-dialog__footer">
<button type="button" class="access-btn access-btn--secondary"
onclick="document.getElementById('create-dialog').close()">Annuler</button>
<button type="submit" class="access-btn">Créer le lien</button>
</div>
</form>
</dialog>
<!-- ═══════════════════════ PASSWORD DIALOG ═══════════════════════ -->
<dialog id="password-dialog" class="access-dialog">
<div class="access-dialog__header">
<h2>Mot de passe</h2>
<button type="button" class="access-dialog__close" onclick="document.getElementById('password-dialog').close()">&#x2715;</button>
</div>
<form method="post" action="actions/student-access.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="set_password">
<input type="hidden" name="id" id="password-link-id" value="">
<div class="access-dialog__body">
<label>
Nouveau mot de passe
<input type="password" name="password" autocomplete="new-password">
<small>Laissez vide pour supprimer le mot de passe.</small>
</label>
<p id="password-current-info" style="font-size:.85rem;color:#64748b;margin-top:.5rem;"></p>
</div>
<div class="access-dialog__footer">
<button type="button" class="access-btn access-btn--secondary"
onclick="document.getElementById('password-dialog').close()">Annuler</button>
<button type="submit" class="access-btn">Enregistrer</button>
</div>
</form>
</dialog>
<script>
document.getElementById('open-create-dialog').addEventListener('click', () => {
document.getElementById('create-dialog').showModal();
});
function copyUrl(id) {
const input = document.getElementById('url-' + id);
input.classList.toggle('visible');
if (input.classList.contains('visible')) {
input.select();
navigator.clipboard.writeText(input.value).then(() => {
// Brief visual feedback
input.style.outline = '2px solid #22c55e';
setTimeout(() => { input.style.outline = ''; }, 600);
});
}
}
function openPasswordDialog(id, hasPassword) {
document.getElementById('password-link-id').value = id;
const info = document.getElementById('password-current-info');
info.textContent = hasPassword
? 'Un mot de passe est actuellement configuré. Entrez-en un nouveau ou laissez vide pour le supprimer.'
: 'Aucun mot de passe configuré.';
document.getElementById('password-dialog').showModal();
}
</script>
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>