diff --git a/TODO.md b/TODO.md index 6f8a002..57e3357 100644 --- a/TODO.md +++ b/TODO.md @@ -1,29 +1,9 @@ -- [x] Analyze nginx/docs and nginx/scripts for deduplication opportunities -- [x] Deduplicate nginx documentation files (removed DEPLOYMENT_COMPLETE.md, DEPLOY_NOW.md) -- [x] Remove duplicate scripts (deploy-production.sh, deploy-production-new.sh, manage-admin-users.sh) -- [x] Update justfile with new script paths -- [x] Update top-level README.md -- [x] Update nginx/README.md -- [x] Create nginx/SETUP.md -- [x] Create top-level SETUP.md -- [x] Update documentation paths (/var/www/html/ → /var/www/posterg/, /formulaire/ → /admin/) -- [x] Remove nginx/scripts/ entirely (install-php-sqlite.sh was duplicate, fix-paths.sh was stale, setup-password.sh superseded by manage-admin-users.sh) -- [x] Fix typo HTACCESS_TO_ NGINX.md → HTACCESS_TO_NGINX.md in nginx/README.md -- [x] Fix nginx/SETUP.md manual step to use just manage-admin-users instead of raw htpasswd -- [x] Fix root README.md dead reference to docs/TODO.SECURITY.md -- [x] Update root README.md project structure (remove nginx/scripts/ entry) -- [x] Form default visibility: "Interne" (access_type_id=2) set at DB insert level -- [x] New TFE always created unpublished (is_published=0 hardcoded in createThesis) -- [x] Contact checkbox: `show_contact` column on authors; checkbox in add/edit forms; tfe.php shows contact only if enabled -- [x] Migration 008: site_settings table + show_contact column + rebuilt views with author_email/author_show_contact/access_type_id -- [x] Formulaire section in parametres.php: toggle switches for Interdit/Interne/Libre access types -- [x] Libre option disabled by default (access_type_libre_enabled=0) -- [x] Add visibility select in add.php, filtered by enabled access types, defaulting to Interne -- [x] Edit.php: pre-populate contact email from DB; show contact_public checkbox with current state -- [x] tfe.php: contact shown from author_email+show_contact; baiu_link relabeled as "Lien" -- [x] actions/settings.php: handler for formulaire settings form -- [x] CSS: admin-toggle pill switches + admin-settings-toggles layout + admin-form-group -- [x] Fix undefined $from– variable in admin/index.php (brace-interpolate around en-dash) -- [x] Add delete single entry to admin table (delete action + handler) -- [x] Add batch delete to bulk actions bar -- [x] Add sortable columns to admin table (click column headers to sort) +# TODO + +## Paramètres page cleanup +- [x] Remove card syntax (`.admin-settings-section` border/radius containers) +- [x] Replace pill toggles with native semantic checkboxes inside `
` +- [x] Move "delete all TFE" danger zone into Maintenance section +- [x] Use `
` for danger zones (semantic, with `` instead of `
`) +- [x] Update CSS: new `.param-*` classes for flat semantic layout +- [x] Exclude parametres sections from generic `.admin-body main > section` card styling via `aria-labelledby` prefix diff --git a/public/admin/actions/delete.php b/public/admin/actions/delete.php index 358e1ae..2c8c237 100644 --- a/public/admin/actions/delete.php +++ b/public/admin/actions/delete.php @@ -14,12 +14,17 @@ if (!isset($_POST['csrf_token'], $_SESSION['csrf_token']) exit; } -$isBulk = !empty($_POST['bulk']); +$isBulk = !empty($_POST['bulk']); +$isDeleteAll = !empty($_POST['delete_all']); try { $db = new Database(); - if ($isBulk) { + if ($isDeleteAll) { + $count = $db->deleteAllTheses(); + App::flash('success', "$count TFE(s) supprimé(s) avec succès."); + + } elseif ($isBulk) { $ids = array_filter(array_map('intval', $_POST['selected_theses'] ?? []), fn($id) => $id > 0); if (empty($ids)) { diff --git a/public/admin/index.php b/public/admin/index.php index 23143c4..ad134ad 100644 --- a/public/admin/index.php +++ b/public/admin/index.php @@ -385,7 +385,7 @@ document.addEventListener('DOMContentLoaded', () => { $from = $offset + 1; $to = min($offset + $perPage, $totalCount); if ($totalPages > 1) { - echo "{$from}–{$to} sur {$totalCount} TFE"; + echo "{$from}-{$to} sur {$totalCount} TFE"; } else { echo "$totalCount TFE"; } @@ -499,21 +499,24 @@ document.addEventListener('DOMContentLoaded', () => { onclick="document.getElementById('import-dialog').close()">✕
- -
- + -
-

Compte administrateur

+
+

Compte administrateur

- -
diff --git a/public/assets/css/admin.css b/public/assets/css/admin.css index df19404..296e4d6 100644 --- a/public/assets/css/admin.css +++ b/public/assets/css/admin.css @@ -650,37 +650,56 @@ } /* ── Thesis info sections (thanks page) ─────────────────────────────────── */ -.admin-body main > section { +.admin-body main > section:not([aria-labelledby^="settings-"]) { border: 1px solid var(--border-primary); border-radius: 6px; padding: var(--space-m); margin-bottom: var(--space-m); } -.admin-body main > section h2 { +.admin-body main > section:not([aria-labelledby^="settings-"]) h2 { margin: 0 0 var(--space-s); font-size: var(--step-1); border-bottom: 1px solid var(--border-primary); padding-bottom: var(--space-2xs); } -.admin-body main > section dl { +.admin-body main > section:not([aria-labelledby^="settings-"]) dl { display: grid; grid-template-columns: 180px 1fr; gap: var(--space-3xs) var(--space-s); } -.admin-body main > section dt { +.admin-body main > section:not([aria-labelledby^="settings-"]) dt { font-weight: 600; font-size: var(--step--1); color: var(--text-secondary); } -.admin-body main > section dd { +.admin-body main > section:not([aria-labelledby^="settings-"]) dd { margin: 0; font-size: var(--step--1); } +/* ── Paramètres page top-level sections (flat, no border card) ──────────── */ +.admin-body main > section[aria-labelledby^="settings-"] { + border: none; + border-radius: 0; + padding: 0; + margin-bottom: var(--space-xl); +} + +.admin-body main > section[aria-labelledby^="settings-"] > h2 { + font-size: var(--step-1); + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-secondary); + margin: 0 0 var(--space-m); + padding-bottom: var(--space-2xs); + border-bottom: 1px solid var(--border-primary); +} + .admin-action-bar { margin-top: var(--space-m); display: flex; @@ -809,26 +828,69 @@ width: 100%; } -/* ── Import page ────────────────────────────────────────────────────────── */ -.admin-import-area { - display: flex; - flex-direction: column; - gap: var(--space-m); +/* ════ Import status card (dialog) ═══════════════════════════════════════ */ +.admin-import-status-card { + margin: 0; + padding: var(--space-m) var(--space-l); } -/* Error list inside role="alert" (import page) */ -.admin-error-list { +.admin-import-status-card__success { + background: var(--success-muted-bg); + border: 1px solid var(--success); + border-left: 3px solid var(--success); + border-radius: 4px; + padding: var(--space-xs) var(--space-s); + font-size: var(--step--1); + margin: 0; +} + +.admin-import-status-card__errors { + background: var(--accent-muted); + border: 1px solid var(--error); + border-left: 3px solid var(--error); + border-radius: 4px; + padding: var(--space-xs) var(--space-s); + font-size: var(--step--1); + margin-bottom: var(--space-s); +} + +.admin-import-status-card__errors .admin-error-list { margin: var(--space-2xs) 0 0; padding-left: var(--space-s); } -/* Hint text under the file input (import page) */ +/* Import log details/summary */ +.admin-import-log-details { + margin: 0; + padding: 0 var(--space-l) var(--space-m); +} + +.admin-import-log-details summary { + cursor: pointer; + font-size: var(--step--1); + font-weight: 600; + color: var(--text-secondary); + padding: var(--space-2xs) 0; + border-top: 1px solid var(--border-primary); +} + +.admin-import-log-details summary:hover { + color: var(--text-primary); +} + +/* Hint text under the file input */ .admin-file-hint { display: block; margin-top: var(--space-2xs); } -/* Import results panel */ +/* Error list inside role="alert" */ +.admin-error-list { + margin: var(--space-2xs) 0 0; + padding-left: var(--space-s); +} + +/* Import results panel (legacy, kept for backward compat) */ .admin-import-results { margin-top: var(--space-l); } @@ -1080,7 +1142,237 @@ .admin-import-log__item--skip::before { content: '⚠'; color: var(--warning); } .admin-import-log__item--error::before { content: '✗'; color: var(--error); } -/* ── Settings page sections ─────────────────────────────────────────────── */ +/* ── Paramètres page (flat, semantic) ──────────────────────────────────── */ +.param-maintenance-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-s); + padding: var(--space-xs) var(--space-m); + margin-bottom: var(--space-m); + font-size: var(--step--1); + border: 1px solid var(--border-primary); + border-radius: 4px; + background: var(--bg-secondary); +} + +.param-maintenance-row:has( .param-btn-warning ) { + background: var(--warning-muted-bg); + border-color: var(--warning); +} + +.param-maintenance-row p { + margin: 0; +} + +.param-maintenance-row form { + flex-shrink: 0; +} + +.param-form { + display: flex; + flex-direction: column; + gap: var(--space-m); +} + +.param-form fieldset { + border: 1px solid var(--border-primary); + border-radius: 4px; + padding: var(--space-m); + background: var(--bg-secondary); + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.param-form legend { + font-weight: 600; + font-size: var(--step--1); + color: var(--text-secondary); + padding: 0 var(--space-xs); +} + +.param-checkbox { + display: flex; + align-items: flex-start; + gap: var(--space-xs); + font-size: var(--step--1); + cursor: pointer; +} + +.param-checkbox--disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.param-checkbox input[type="checkbox"] { + accent-color: var(--accent-primary); + width: 16px; + height: 16px; + cursor: pointer; + flex-shrink: 0; + margin-top: 2px; +} + +.param-checkbox--disabled input[type="checkbox"] { + cursor: not-allowed; +} + +.param-checkbox small { + color: var(--text-secondary); + font-size: var(--step--2); +} + +.param-note { + font-size: var(--step--1); + color: var(--text-secondary); + margin: 0; +} + +.param-account-status { + display: flex; + flex-direction: column; + gap: var(--space-xs); + font-size: var(--step--1); + margin-bottom: var(--space-m); +} + +.param-account-status > div { + display: flex; + align-items: center; + gap: var(--space-xs); +} + +.param-account-status dt { + color: var(--text-secondary); + min-width: 200px; +} + +.param-account-status dd { + margin: 0; + display: flex; + align-items: center; + gap: var(--space-xs); +} + +.param-account-status code { + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: var(--step--2); + border: 1px solid var(--border-primary); + border-radius: 3px; + padding: var(--space-3xs); + color: var(--text-secondary); + background: var(--bg-secondary); +} + +.param-form > div { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-3xs); + border-top: 1px solid var(--border-primary); + padding: var(--space-xs) 0; +} + +.param-form > div:first-of-type { + border-top: none; + padding-top: 0; +} + +.param-form input[type="password"] { + width: 100%; + max-width: 380px; + background: transparent; + border: none; + border-bottom: 1px solid var(--border-primary); + font-size: var(--step--1); + font-family: inherit; + padding: var(--space-3xs) 0; + border-radius: 0; + transition: border-color 0.15s; +} + +.param-form input[type="password"]:focus { + outline: none; + border-bottom-color: var(--accent-primary); +} + +.param-form > button { + align-self: flex-start; + padding: var(--space-2xs) var(--space-l); + background: var(--accent-primary); + color: var(--accent-foreground); + border: none; + border-radius: 3px; + font-size: var(--step--1); + font-family: inherit; + cursor: pointer; + letter-spacing: 0.04em; + transition: background 0.15s; +} + +.param-form > button:hover { + background: var(--accent-secondary); +} + +.param-btn-warning { + padding: var(--space-3xs) var(--space-s); + background: var(--accent-yellow); + color: var(--text-primary); + border: none; + border-radius: 3px; + font-size: var(--step--1); + font-family: inherit; + cursor: pointer; + transition: filter 0.15s; +} + +.param-btn-warning:hover { + filter: brightness(0.9); +} + +.param-btn-danger { + padding: var(--space-3xs) var(--space-s); + background: var(--accent-red); + color: var(--accent-foreground); + border: none; + border-radius: 3px; + font-size: var(--step--1); + font-family: inherit; + cursor: pointer; + transition: filter 0.15s; +} + +.param-btn-danger:hover { + filter: brightness(0.9); +} + +.param-danger-zone { + border: 1px solid var(--danger-border-muted); + border-radius: 4px; + padding: var(--space-m); + margin-bottom: var(--space-m); + background: var(--bg-secondary); +} + +.param-danger-zone legend { + width: auto; + font-weight: 600; + color: var(--error); + font-size: var(--step-0); + padding: 0 var(--space-xs); +} + +.param-danger-zone p { + font-size: var(--step--1); + color: var(--text-secondary); + margin-bottom: var(--space-xs); +} + +.param-danger-zone form { + margin-top: var(--space-xs); +} + +/* ── Settings page sections — legacy aliases (kept for any remaining use) ─ */ .admin-settings-section { border: 1px solid var(--border-primary); border-radius: 6px; @@ -1125,6 +1417,130 @@ flex-shrink: 0; } +.admin-account-status { + border: 1px solid var(--border-primary); + border-radius: 4px; + padding: var(--space-s) var(--space-m); + margin-bottom: var(--space-l); + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.admin-account-status__row { + display: flex; + align-items: center; + gap: var(--space-xs); + font-size: var(--step--1); +} + +.admin-account-status__label { + color: var(--text-secondary); + min-width: 220px; +} + +.admin-account-status__code { + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: var(--step--2); + border: 1px solid var(--border-primary); + border-radius: 3px; + padding: var(--space-3xs) var(--space-3xs); + color: var(--text-secondary); + background: var(--bg-secondary); +} + +.admin-account-status__note { + font-size: var(--step--1); + color: var(--text-secondary); + margin: 0; +} + +.admin-danger-zone { + border: 1px solid var(--danger-border-muted); + border-radius: 4px; + padding: var(--space-m) var(--space-m); + display: flex; + align-items: center; + gap: var(--space-m); + flex-wrap: wrap; +} + +.admin-danger-zone__description { + flex: 1; + font-size: var(--step--1); +} + +.admin-settings-toggles { + display: flex; + flex-direction: column; + gap: var(--space-xs); + margin-bottom: var(--space-m); +} + +.admin-toggle-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-m); + background: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 4px; + padding: var(--space-xs) var(--space-m); + cursor: pointer; +} + +.admin-toggle-row--disabled { + opacity: 0.6; +} + +.admin-toggle-label { + display: flex; + flex-direction: column; + gap: 2px; +} + +.admin-toggle-label strong { + font-size: var(--step-0); +} + +.admin-toggle-label small { + color: var(--text-secondary); + font-size: var(--step--2); +} + +.admin-toggle { + appearance: none; + -webkit-appearance: none; + width: 40px; + height: 22px; + background: var(--border-primary); + border-radius: 11px; + position: relative; + cursor: pointer; + flex-shrink: 0; + transition: background 0.2s; +} + +.admin-toggle::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 16px; + height: 16px; + border-radius: 50%; + background: #fff; + transition: transform 0.2s; +} + +.admin-toggle:checked { + background: var(--accent-primary); +} + +.admin-toggle:checked::after { + transform: translateX(18px); +} + /* ── Cancel link ────────────────────────────────────────────────────────── */ .admin-cancel-link { font-size: var(--step--1); diff --git a/src/Database.php b/src/Database.php index f9da3e3..a9eebd8 100644 --- a/src/Database.php +++ b/src/Database.php @@ -1659,6 +1659,17 @@ class Database { return (string)$row['identifier']; } + /** + * Delete every thesis in the database. + */ + public function deleteAllTheses(): int { + $ids = $this->pdo->query("SELECT id FROM theses")->fetchAll(\PDO::FETCH_COLUMN); + if (empty($ids)) return 0; + $count = count($ids); + $this->bulkDeleteTheses($ids); + return $count; + } + /** * Insert a thesis file record */