Merge pull request #85 from quoije/2026
Refine admin control layout and stats tabs
This commit was merged in pull request #85.
This commit is contained in:
+348
-452
@@ -27,6 +27,185 @@
|
|||||||
.navbar-toggler-icon {
|
.navbar-toggler-icon {
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .info-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
line-height: 1;
|
||||||
|
align-self: center;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .info-line .badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .info-line .info-text {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .btn-compact {
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .btn-check-compact {
|
||||||
|
padding-left: 0.35rem;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .app-version-create {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .app-version-create .fw-semibold {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .backup-restore-panel {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .backup-restore-panel .form-label,
|
||||||
|
.admin-tabs-content .backup-restore-panel .form-check-label {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .backup-restore-panel .alert {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .btn-check-small {
|
||||||
|
height: 1.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .oscar-date-year-select {
|
||||||
|
max-width: 220px;
|
||||||
|
width: 220px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .manage-year-select {
|
||||||
|
max-width: 110px;
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .oscar-date-input {
|
||||||
|
max-width: 220px;
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .points-input-half {
|
||||||
|
max-width: 110px;
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .oscar-date-layout {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .oscar-date-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .oscar-date-fields .form-text {
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .oscar-date-current {
|
||||||
|
display: inline-block;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0.4rem 0.65rem;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .btn-check-wide {
|
||||||
|
padding-left: 0.55rem;
|
||||||
|
padding-right: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content h4,
|
||||||
|
.admin-tabs-content h5 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-subtabs .nav-link {
|
||||||
|
padding: 0.3rem 0.7rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html[data-theme="dark"] #adminAddMovieAccordion .accordion-item {
|
||||||
|
background-color: var(--app-surface-alt);
|
||||||
|
border-color: var(--app-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] #adminAddMovieAccordion .accordion-button {
|
||||||
|
background-color: var(--app-surface-alt);
|
||||||
|
color: var(--app-text);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] #adminAddMovieAccordion .accordion-button::after {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] #adminAddMovieAccordion .accordion-button:not(.collapsed) {
|
||||||
|
background-color: var(--app-surface);
|
||||||
|
color: var(--app-text);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] #adminAddMovieAccordion .accordion-body {
|
||||||
|
background-color: var(--app-surface);
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-movies-add {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-movies-separator {
|
||||||
|
border-top: 1px solid var(--app-border);
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-movies-meta {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-subtabs-tight {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-tabs-content .table,
|
||||||
|
.admin-tabs-content .table th,
|
||||||
|
.admin-tabs-content .table td {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
@@ -46,7 +225,7 @@
|
|||||||
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
|
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
|
||||||
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
|
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
|
||||||
<a href="picks.html" class="nav-link btn btn-outline-light me-2" id="nav-link-picks" style="display: none;">Mes picks</a>
|
<a href="picks.html" class="nav-link btn btn-outline-light me-2" id="nav-link-picks" style="display: none;">Mes picks</a>
|
||||||
<a href="control.html" class="nav-link btn btn-warning me-2">Admin</a>
|
<a href="control.html" class="nav-link btn btn-outline-light me-2 active">Admin</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center gap-2 navbar-user">
|
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center gap-2 navbar-user">
|
||||||
<span id="user-name" class="navbar-text" data-user-name></span>
|
<span id="user-name" class="navbar-text" data-user-name></span>
|
||||||
@@ -77,7 +256,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container mt-4 pb-5">
|
||||||
<div id="response" class="alert d-none" role="alert"></div>
|
<div id="response" class="alert d-none" role="alert"></div>
|
||||||
|
|
||||||
<ul class="nav nav-tabs" id="adminTabs" role="tablist">
|
<ul class="nav nav-tabs" id="adminTabs" role="tablist">
|
||||||
@@ -123,32 +302,22 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content pt-3">
|
<div class="tab-content pt-3 admin-tabs-content">
|
||||||
<div class="tab-pane fade show active" id="admin-settings" role="tabpanel" aria-labelledby="admin-settings-tab">
|
<div class="tab-pane fade show active" id="admin-settings" role="tabpanel" aria-labelledby="admin-settings-tab">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||||||
<div>
|
<div class="info-line small text-muted">
|
||||||
<h4 class="mb-1">Réglages</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">Paramètres globaux du site (année, dates, options du lecteur).</div>
|
<span class="info-text">Paramètres globaux du site (année, dates, options du lecteur).</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminSettingsSubTabs" role="tablist">
|
<ul class="nav nav-pills admin-subtabs mt-3" id="adminSettingsSubTabs" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active" id="admin-settings-subtab-year" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-year" type="button" role="tab" aria-controls="admin-settings-pane-year" aria-selected="true">
|
<button class="nav-link active" id="admin-settings-subtab-oscar-date" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-oscar-date" type="button" role="tab" aria-controls="admin-settings-pane-oscar-date" aria-selected="true">
|
||||||
Année active
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="admin-settings-subtab-oscar-date" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-oscar-date" type="button" role="tab" aria-controls="admin-settings-pane-oscar-date" aria-selected="false">
|
|
||||||
Date Oscars
|
Date Oscars
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="admin-settings-subtab-points" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-points" type="button" role="tab" aria-controls="admin-settings-pane-points" aria-selected="false">
|
|
||||||
Points
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="admin-settings-subtab-visibility" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-visibility" type="button" role="tab" aria-controls="admin-settings-pane-visibility" aria-selected="false">
|
<button class="nav-link" id="admin-settings-subtab-visibility" data-bs-toggle="tab" data-bs-target="#admin-settings-pane-visibility" type="button" role="tab" aria-controls="admin-settings-pane-visibility" aria-selected="false">
|
||||||
Visibilité
|
Visibilité
|
||||||
@@ -157,88 +326,78 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content pt-3">
|
<div class="tab-content pt-3">
|
||||||
<!-- Active year -->
|
<!-- Oscar date + points -->
|
||||||
<div class="tab-pane fade show active" id="admin-settings-pane-year" role="tabpanel" aria-labelledby="admin-settings-subtab-year" tabindex="0">
|
<div class="tab-pane fade show active" id="admin-settings-pane-oscar-date" role="tabpanel" aria-labelledby="admin-settings-subtab-oscar-date" tabindex="0">
|
||||||
<div class="border rounded p-3 bg-light">
|
<div class="row g-3">
|
||||||
<div class="text-muted small mb-3">
|
<div class="col-12 col-xxl-6">
|
||||||
Pilote tout le site (films, checklist, statistiques).
|
<div class="border rounded p-3 bg-light h-100">
|
||||||
</div>
|
<div class="text-muted small mb-3">
|
||||||
<label for="active_oscar_year" class="form-label">Année active</label>
|
Configure la date de cérémonie pour le compte à rebours (Films & Checklist). Défaut: <code>15 mars</code>.
|
||||||
<div class="admin-settings-narrow">
|
</div>
|
||||||
<div class="input-group">
|
|
||||||
<select class="form-select" id="active_oscar_year"></select>
|
|
||||||
<button type="button" id="save-active-year" class="btn btn-warning">Appliquer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-text">Basé sur les années présentes dans les films.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Oscar date -->
|
<div class="oscar-date-layout">
|
||||||
<div class="tab-pane fade" id="admin-settings-pane-oscar-date" role="tabpanel" aria-labelledby="admin-settings-subtab-oscar-date" tabindex="0">
|
<div class="oscar-date-fields">
|
||||||
<div class="border rounded p-3 bg-light">
|
<div>
|
||||||
<div class="text-muted small mb-3">
|
<label for="oscar_date_year" class="form-label visually-hidden">Année</label>
|
||||||
Configure la date de cérémonie pour le compte à rebours (Films & Checklist). Défaut: <code>15 mars</code>.
|
<div class="input-group align-items-center">
|
||||||
</div>
|
<select class="form-select oscar-date-year-select" id="oscar_date_year"></select>
|
||||||
|
<button type="button" id="save-active-year" class="btn btn-warning btn-compact btn-check-compact btn-check-small btn-check-wide rounded-3 ms-1" aria-label="Appliquer l'année active">✓</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Basé sur les années présentes dans les films.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="oscar_date_value" class="form-label visually-hidden">Date</label>
|
||||||
|
<div class="input-group align-items-center">
|
||||||
|
<input type="date" class="form-control oscar-date-input" id="oscar_date_value">
|
||||||
|
<button type="button" id="save-oscar-date" class="btn btn-warning btn-compact btn-check-compact btn-check-small btn-check-wide rounded-3 ms-1" aria-label="Enregistrer">✓</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Format: YYYY-MM-DD.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row g-3 align-items-end">
|
<div class="mt-3">
|
||||||
<div class="col-12 col-md-4">
|
<div class="text-muted small mb-1">Dates configurées</div>
|
||||||
<label for="oscar_date_year" class="form-label">Année</label>
|
<div id="oscar-date-current" class="border rounded bg-white oscar-date-current">—</div>
|
||||||
<select class="form-select" id="oscar_date_year"></select>
|
</div>
|
||||||
<div class="form-text">Basé sur les années présentes dans les films.</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="oscar_date_value" class="form-label">Date</label>
|
|
||||||
<input type="date" class="form-control" id="oscar_date_value">
|
|
||||||
<div class="form-text">Format: YYYY-MM-DD.</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4 d-flex gap-2 justify-content-end">
|
|
||||||
<button type="button" id="clear-oscar-date" class="btn btn-outline-danger w-100">Effacer</button>
|
|
||||||
<button type="button" id="save-oscar-date" class="btn btn-warning w-100">Enregistrer</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="col-12 col-xxl-6">
|
||||||
<div class="text-muted small mb-1">Dates configurées</div>
|
<div class="border rounded p-3 bg-light h-100">
|
||||||
<div id="oscar-date-current" class="border rounded p-2 bg-white">—</div>
|
<div class="text-muted small mb-3">
|
||||||
</div>
|
Configure les points attribués pour les films regardés et les bonnes réponses aux Oscars.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Points configuration -->
|
<div id="points-config-alert" class="alert d-none" role="alert"></div>
|
||||||
<div class="tab-pane fade" id="admin-settings-pane-points" role="tabpanel" aria-labelledby="admin-settings-subtab-points" tabindex="0">
|
|
||||||
<div class="border rounded p-3 bg-light">
|
|
||||||
<div class="text-muted small mb-3">
|
|
||||||
Configure les points attribués pour les films regardés et les bonnes réponses aux Oscars.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="points-config-alert" class="alert d-none" role="alert"></div>
|
<div class="row g-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="points-per-movie" class="form-label">Points par film regardé</label>
|
||||||
|
<input type="number" class="form-control points-input-half" id="points-per-movie" min="0" step="1" value="1">
|
||||||
|
<div class="form-text">Points attribués pour chaque film complété.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="points-per-correct-pick" class="form-label">Points par bonne réponse</label>
|
||||||
|
<input type="number" class="form-control points-input-half" id="points-per-correct-pick" min="0" step="1" value="1">
|
||||||
|
<div class="form-text">Points attribués pour chaque bonne réponse aux Oscars.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="mt-3">
|
||||||
<div class="col-12 col-md-6">
|
<div class="d-flex gap-2">
|
||||||
<label for="points-per-movie" class="form-label">Points par film regardé</label>
|
<button type="button" id="points-config-reload" class="btn btn-outline-secondary btn-compact">Recharger</button>
|
||||||
<input type="number" class="form-control" id="points-per-movie" min="0" step="1" value="1">
|
<button type="button" id="points-config-save" class="btn btn-warning btn-compact">Enregistrer</button>
|
||||||
<div class="form-text">Points attribués pour chaque film complété.</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
|
||||||
<label for="points-per-correct-pick" class="form-label">Points par bonne réponse</label>
|
|
||||||
<input type="number" class="form-control" id="points-per-correct-pick" min="0" step="1" value="1">
|
|
||||||
<div class="form-text">Points attribués pour chaque bonne réponse aux Oscars.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div class="d-flex gap-2">
|
<div class="text-muted small mb-1">Calcul des points</div>
|
||||||
<button type="button" id="points-config-reload" class="btn btn-outline-secondary">Recharger</button>
|
<div class="border rounded p-2 bg-white">
|
||||||
<button type="button" id="points-config-save" class="btn btn-warning">Enregistrer</button>
|
<div class="small">
|
||||||
</div>
|
<strong>Total =</strong> (Films regardés × <span id="points-per-movie-preview">1</span>) + (Bonnes réponses × <span id="points-per-correct-pick-preview">1</span>)
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="mt-3">
|
|
||||||
<div class="text-muted small mb-1">Calcul des points</div>
|
|
||||||
<div class="border rounded p-2 bg-white">
|
|
||||||
<div class="small">
|
|
||||||
<strong>Total =</strong> (Films regardés × <span id="points-per-movie-preview">1</span>) + (Bonnes réponses × <span id="points-per-correct-pick-preview">1</span>)
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -267,8 +426,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="button" id="visibility-config-reload" class="btn btn-outline-secondary">Recharger</button>
|
<button type="button" id="visibility-config-save" class="btn btn-warning btn-compact">Enregistrer</button>
|
||||||
<button type="button" id="visibility-config-save" class="btn btn-warning">Enregistrer</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,125 +437,46 @@
|
|||||||
<div class="tab-pane fade" id="admin-version" role="tabpanel" aria-labelledby="admin-version-tab">
|
<div class="tab-pane fade" id="admin-version" role="tabpanel" aria-labelledby="admin-version-tab">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||||||
<div>
|
<div class="info-line small text-muted">
|
||||||
<h4 class="mb-1">Version</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">Contrôle la version affichée dans le footer (stockée en DB).</div>
|
<span class="info-text">Contrôle la version affichée dans le footer (stockée en DB). Clique sur ✓ pour activer une version, puis vérifie l’aperçu.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="app_version_alert" class="alert d-none mt-3 mb-0" role="alert"></div>
|
<div id="app_version_alert" class="alert d-none mt-3 mb-0" role="alert"></div>
|
||||||
|
|
||||||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminVersionSubTabs" role="tablist">
|
<div class="pt-3">
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link active" id="admin-version-subtab-active" data-bs-toggle="tab" data-bs-target="#admin-version-pane-active" type="button" role="tab" aria-controls="admin-version-pane-active" aria-selected="true">
|
|
||||||
Active
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="admin-version-subtab-history" data-bs-toggle="tab" data-bs-target="#admin-version-pane-history" type="button" role="tab" aria-controls="admin-version-pane-history" aria-selected="false">
|
|
||||||
Historique
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="admin-version-subtab-create" data-bs-toggle="tab" data-bs-target="#admin-version-pane-create" type="button" role="tab" aria-controls="admin-version-pane-create" aria-selected="false">
|
|
||||||
Nouvelle version
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content pt-3">
|
|
||||||
<!-- Active -->
|
|
||||||
<div class="tab-pane fade show active" id="admin-version-pane-active" role="tabpanel" aria-labelledby="admin-version-subtab-active" tabindex="0">
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-xxl-7">
|
<div class="col-12 col-xxl-7">
|
||||||
<div class="border rounded p-3 bg-light">
|
<div id="app_version_list" class="border rounded bg-white app-version-list">—</div>
|
||||||
<div class="fw-semibold mb-1">Version active</div>
|
|
||||||
<div class="text-muted small mb-3">
|
|
||||||
Met à jour ce que les utilisateurs voient dans le footer.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label for="app_version_active_select" class="form-label">Choisir une version</label>
|
|
||||||
<div class="row g-2 align-items-end">
|
|
||||||
<div class="col-12 col-lg-8">
|
|
||||||
<select id="app_version_active_select" class="form-select"></select>
|
|
||||||
<div class="form-text">La liste vient de l’historique stocké en DB.</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-4 d-flex gap-2">
|
|
||||||
<button type="button" id="app_version_reload" class="btn btn-outline-secondary w-100">Recharger</button>
|
|
||||||
<button type="button" id="app_version_set_active" class="btn btn-warning w-100">Activer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3">
|
|
||||||
<div class="text-muted small mb-1">Aperçu (footer)</div>
|
|
||||||
<div id="app_version_preview" class="border rounded p-2 bg-white app-version-preview">—</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-xxl-5">
|
<div class="col-12 col-xxl-5">
|
||||||
<div class="border rounded p-3 bg-white">
|
<div class="border rounded p-3 bg-light app-version-create">
|
||||||
<div class="fw-semibold mb-2">Bon à savoir</div>
|
|
||||||
<ul class="mb-0 text-muted">
|
|
||||||
<li>“Recharger” relit l’historique depuis l’API.</li>
|
|
||||||
<li>“Activer” change la version active en DB.</li>
|
|
||||||
<li>Le footer lit ensuite la version via <code>/api/version</code>.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- History -->
|
|
||||||
<div class="tab-pane fade" id="admin-version-pane-history" role="tabpanel" aria-labelledby="admin-version-subtab-history" tabindex="0">
|
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
|
||||||
<div>
|
|
||||||
<div class="fw-semibold">Historique</div>
|
|
||||||
<div class="text-muted small">Supprime les entrées obsolètes si besoin.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="app_version_list" class="border rounded bg-white app-version-list">—</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Create -->
|
|
||||||
<div class="tab-pane fade" id="admin-version-pane-create" role="tabpanel" aria-labelledby="admin-version-subtab-create" tabindex="0">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-xxl-7">
|
|
||||||
<div class="border rounded p-3 bg-light">
|
|
||||||
<div class="fw-semibold mb-1">Créer une nouvelle version</div>
|
<div class="fw-semibold mb-1">Créer une nouvelle version</div>
|
||||||
<div class="text-muted small mb-3">
|
<div class="text-muted small mb-3">
|
||||||
Ajoute une entrée dans l’historique (tu peux l’activer immédiatement).
|
Ajoute une entrée dans l’historique (tu peux l’activer immédiatement).
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-5">
|
||||||
<label for="app_version_new_version" class="form-label">Version</label>
|
<label for="app_version_new_version" class="form-label">Version</label>
|
||||||
<input id="app_version_new_version" type="text" class="form-control" placeholder="ex: 1.0.1" maxlength="80">
|
<input id="app_version_new_version" type="text" class="form-control" placeholder="ex: 1.0.1" maxlength="80">
|
||||||
<div class="form-text">Ex: <code>1.2.3</code></div>
|
<div class="form-text">Ex: <code>1.2.3</code></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-8">
|
<div class="col-12 col-md-7">
|
||||||
<label for="app_version_new_message" class="form-label">Message (optionnel)</label>
|
<label for="app_version_new_message" class="form-label">Message (optionnel)</label>
|
||||||
<input id="app_version_new_message" type="text" class="form-control" placeholder="ex: Hotfix login" maxlength="500">
|
<input id="app_version_new_message" type="text" class="form-control" placeholder="ex: Hotfix login" maxlength="500">
|
||||||
<div class="form-text">Court & lisible (affiché dans le footer).</div>
|
<div class="form-text">Court & lisible (affiché dans le footer).</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex flex-wrap justify-content-end gap-2">
|
<div class="col-12 d-flex flex-wrap justify-content-end gap-2">
|
||||||
<button type="button" id="app_version_create" class="btn btn-primary">Créer</button>
|
<button type="button" id="app_version_create" class="btn btn-primary btn-compact">Créer</button>
|
||||||
<button type="button" id="app_version_create_and_activate" class="btn btn-warning">Créer & activer</button>
|
<button type="button" id="app_version_create_and_activate" class="btn btn-warning btn-compact">Créer & activer</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-xxl-5">
|
|
||||||
<div class="border rounded p-3 bg-white">
|
|
||||||
<div class="fw-semibold mb-2">Astuce</div>
|
|
||||||
<div class="text-muted">
|
|
||||||
Après création, va dans l’onglet “Active” pour vérifier l’aperçu du footer.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -405,28 +484,15 @@
|
|||||||
<div class="tab-pane fade" id="admin-modal-100" role="tabpanel" aria-labelledby="admin-modal-100-tab">
|
<div class="tab-pane fade" id="admin-modal-100" role="tabpanel" aria-labelledby="admin-modal-100-tab">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||||||
<div>
|
<div class="info-line small text-muted">
|
||||||
<h4 class="mb-1">Modal 100%</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">Contenu affiché quand un utilisateur a vu 100% des films.</div>
|
<span class="info-text">Contenu affiché quand un utilisateur a vu 100% des films.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminCompletionModalSubTabs" role="tablist">
|
<div class="pt-3">
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link active" id="admin-modal-100-subtab-edit" data-bs-toggle="tab" data-bs-target="#admin-modal-100-pane-edit" type="button" role="tab" aria-controls="admin-modal-100-pane-edit" aria-selected="true">
|
|
||||||
Éditer
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="admin-modal-100-subtab-help" data-bs-toggle="tab" data-bs-target="#admin-modal-100-pane-help" type="button" role="tab" aria-controls="admin-modal-100-pane-help" aria-selected="false">
|
|
||||||
Aide
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content pt-3">
|
|
||||||
<!-- Edit + Live preview side-by-side -->
|
<!-- Edit + Live preview side-by-side -->
|
||||||
<div class="tab-pane fade show active" id="admin-modal-100-pane-edit" role="tabpanel" aria-labelledby="admin-modal-100-subtab-edit" tabindex="0">
|
<div>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-xxl-7">
|
<div class="col-12 col-xxl-7">
|
||||||
<div class="border rounded p-3 bg-light h-100">
|
<div class="border rounded p-3 bg-light h-100">
|
||||||
@@ -461,56 +527,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||||||
<button type="button" id="completion_modal_reload" class="btn btn-outline-secondary">Recharger</button>
|
<button type="button" id="completion_modal_save" class="btn btn-warning btn-compact">Enregistrer</button>
|
||||||
<button type="button" id="completion_modal_save" class="btn btn-warning">Enregistrer</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-xxl-5">
|
<div class="col-12 col-xxl-5">
|
||||||
<div class="border rounded p-3 bg-white h-100">
|
|
||||||
<div class="d-flex align-items-center justify-content-between gap-2 mb-2">
|
|
||||||
<div class="fw-semibold">Aperçu</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted small mb-3">Aperçu live (non sauvegardé).</div>
|
|
||||||
|
|
||||||
<div class="border rounded p-3 bg-light">
|
|
||||||
<div class="d-flex align-items-center justify-content-between gap-2 mb-2">
|
|
||||||
<div class="fw-semibold" id="completion_modal_preview_title">—</div>
|
|
||||||
</div>
|
|
||||||
<div id="completion_modal_preview_custom" class="text-start d-none"></div>
|
|
||||||
<div id="completion_modal_preview_text" class="text-start" style="white-space: pre-wrap;"></div>
|
|
||||||
|
|
||||||
<div class="mt-3">
|
|
||||||
<video id="completion_modal_preview_video" width="75%" controls class="d-none">
|
|
||||||
<source id="completion_modal_preview_video_source" src="" type="video/mp4">
|
|
||||||
Votre navigateur ne supporte pas la vidéo.
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Help -->
|
|
||||||
<div class="tab-pane fade" id="admin-modal-100-pane-help" role="tabpanel" aria-labelledby="admin-modal-100-subtab-help" tabindex="0">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-xxl-8">
|
|
||||||
<div class="border rounded p-3 bg-white">
|
|
||||||
<div class="fw-semibold mb-2">Règles</div>
|
|
||||||
<ul class="mb-0 text-muted">
|
|
||||||
<li><strong>Texte</strong>: affiché tel quel (safe), retours à la ligne conservés.</li>
|
|
||||||
<li><strong>HTML</strong>: le serveur retire les <code><script></code> et les attributs <code>on*</code>.</li>
|
|
||||||
<li><strong>Vidéo</strong>: accepte un chemin relatif (ex: <code>video/reward.mp4</code>) ou une URL complète.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-xxl-4">
|
|
||||||
<div class="border rounded p-3 bg-light">
|
<div class="border rounded p-3 bg-light">
|
||||||
<div class="fw-semibold mb-2">Astuce</div>
|
<div class="d-flex align-items-center justify-content-between gap-2 mb-2">
|
||||||
<div class="text-muted">
|
<div class="fw-semibold" id="completion_modal_preview_title">—</div>
|
||||||
Si tu utilises du HTML, préfère des tags simples (<code><p></code>, <code><a></code>, <code><strong></code>) pour éviter des surprises d’affichage.
|
</div>
|
||||||
|
<div id="completion_modal_preview_custom" class="text-start d-none"></div>
|
||||||
|
<div id="completion_modal_preview_text" class="text-start" style="white-space: pre-wrap;"></div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<video id="completion_modal_preview_video" width="55%" controls class="d-none">
|
||||||
|
<source id="completion_modal_preview_video_source" src="" type="video/mp4">
|
||||||
|
Votre navigateur ne supporte pas la vidéo.
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded p-3 bg-white mt-3">
|
||||||
|
<ul class="mb-0 text-muted">
|
||||||
|
<li><strong>Texte</strong>: affiché tel quel (safe), retours à la ligne conservés.</li>
|
||||||
|
<li><strong>HTML</strong>: le serveur retire les <code><script></code> et les attributs <code>on*</code>.</li>
|
||||||
|
<li><strong>Vidéo</strong>: accepte un chemin relatif (ex: <code>video/reward.mp4</code>) ou une URL complète.</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -525,15 +567,13 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
||||||
<div>
|
<div class="info-line small text-muted">
|
||||||
<h4 class="mb-1">Liste des utilisateurs</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">
|
<span class="info-text"><span id="admin-users-count">—</span> utilisateur(s)</span>
|
||||||
<span id="admin-users-count">—</span> utilisateur(s)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="button" id="open-add-user-modal" class="btn btn-warning btn-sm">Ajouter un utilisateur</button>
|
<button type="button" id="open-add-user-modal" class="btn btn-warning btn-sm btn-compact">Ajouter un utilisateur</button>
|
||||||
<button type="button" id="refresh-users" class="btn btn-outline-primary btn-sm">Rafraîchir</button>
|
<button type="button" id="refresh-users" class="btn btn-outline-primary btn-sm btn-compact">Rafraîchir</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -559,63 +599,53 @@
|
|||||||
|
|
||||||
<div class="tab-pane fade" id="admin-movies" role="tabpanel" aria-labelledby="admin-movies-tab">
|
<div class="tab-pane fade" id="admin-movies" role="tabpanel" aria-labelledby="admin-movies-tab">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
<div class="admin-movies-add">
|
||||||
<div>
|
<div class="info-line small text-muted mb-3">
|
||||||
<h4 class="mb-1">Films</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">Gestion et ajout des films (par année).</div>
|
<span class="info-text">Ajout de films : cette section récupère automatiquement les infos du film via l’<code>imdb_id</code> (OMDb).</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="add-movie-form">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="oscar_year" class="form-label">Oscar year</label>
|
||||||
|
<input type="number" class="form-control" id="oscar_year" placeholder="2026" min="1900" max="3000" required>
|
||||||
|
<div class="form-text">Sépare films & stats par année.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="imdb_id" class="form-label">IMDB ID</label>
|
||||||
|
<input type="text" class="form-control" id="imdb_id" placeholder="tt1234567" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="category" class="form-label">Catégorie</label>
|
||||||
|
<input type="text" class="form-control" id="category" placeholder="Best Picture, etc." required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex flex-wrap justify-content-end gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary btn-compact">Ajouter</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-movies-separator"></div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 admin-movies-meta">
|
||||||
|
<div class="text-muted small">
|
||||||
|
<span id="admin-movies-table-count"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav nav-pills admin-subtabs mt-3" id="adminMoviesSubTabs" role="tablist">
|
<div class="row g-2 align-items-end">
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link active" id="admin-movies-subtab-manage" data-bs-toggle="tab" data-bs-target="#admin-movies-pane-manage" type="button" role="tab" aria-controls="admin-movies-pane-manage" aria-selected="true">
|
|
||||||
Gérer
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="admin-movies-subtab-add" data-bs-toggle="tab" data-bs-target="#admin-movies-pane-add" type="button" role="tab" aria-controls="admin-movies-pane-add" aria-selected="false">
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link" id="admin-movies-subtab-help" data-bs-toggle="tab" data-bs-target="#admin-movies-pane-help" type="button" role="tab" aria-controls="admin-movies-pane-help" aria-selected="false">
|
|
||||||
Aide
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content pt-3">
|
|
||||||
<!-- Manage -->
|
|
||||||
<div class="tab-pane fade show active" id="admin-movies-pane-manage" role="tabpanel" aria-labelledby="admin-movies-subtab-manage" tabindex="0">
|
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2">
|
|
||||||
<div>
|
|
||||||
<h5 class="mb-1">Gérer les films</h5>
|
|
||||||
<div class="text-muted">Filtre, sélection et suppression en lot.</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted small">
|
|
||||||
<span id="admin-movies-table-count">—</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-2 align-items-end">
|
|
||||||
<div class="col-12 col-md-4 col-xl-3">
|
<div class="col-12 col-md-4 col-xl-3">
|
||||||
<label for="manage_year" class="form-label">Oscar year</label>
|
<label for="manage_year" class="form-label visually-hidden">Oscar year</label>
|
||||||
<select id="manage_year" class="form-select">
|
<select id="manage_year" class="form-select manage-year-select">
|
||||||
<option value="">Toutes</option>
|
<option value="">Toutes</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-8 col-xl-5">
|
<div class="col-12 col-md-8 col-xl-9 d-flex gap-2 justify-content-end">
|
||||||
<label for="admin_movies_search" class="form-label">Recherche</label>
|
<button type="button" id="refresh-movies" class="btn btn-outline-primary btn-compact">Rafraîchir</button>
|
||||||
<div class="input-group">
|
<button type="button" id="delete-selected" class="btn btn-danger btn-compact" disabled>Supprimer</button>
|
||||||
<input type="text" class="form-control" id="admin_movies_search" placeholder="Titre, IMDB ID, catégorie…" autocomplete="off">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" id="admin_movies_clear_search">Effacer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-xl-4 d-flex gap-2">
|
|
||||||
<button type="button" id="refresh-movies" class="btn btn-outline-primary w-100">Rafraîchir</button>
|
|
||||||
<button type="button" id="delete-selected" class="btn btn-danger w-100" disabled>Supprimer</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -647,149 +677,15 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Add -->
|
|
||||||
<div class="tab-pane fade" id="admin-movies-pane-add" role="tabpanel" aria-labelledby="admin-movies-subtab-add" tabindex="0">
|
|
||||||
<h5 class="mb-2">Ajouter un film</h5>
|
|
||||||
<div class="text-muted mb-3">
|
|
||||||
Le serveur récupère automatiquement titre/plot/poster via OMDb avec l’<code>imdb_id</code>.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="add-movie-form">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="oscar_year" class="form-label">Oscar year</label>
|
|
||||||
<input type="number" class="form-control" id="oscar_year" placeholder="2026" min="1900" max="3000" required>
|
|
||||||
<div class="form-text">Sépare films & stats par année.</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="imdb_id" class="form-label">IMDB ID</label>
|
|
||||||
<input type="text" class="form-control" id="imdb_id" placeholder="tt1234567" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="category" class="form-label">Catégorie</label>
|
|
||||||
<input type="text" class="form-control" id="category" placeholder="Best Picture, etc." required>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4 d-flex align-items-end">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="cam_flag">
|
|
||||||
<label class="form-check-label" for="cam_flag">Tag CAM</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="accordion" id="adminAddMovieAccordion">
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="adminAddMovieHeadingPlayer">
|
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#adminAddMovieCollapsePlayer" aria-expanded="true" aria-controls="adminAddMovieCollapsePlayer">
|
|
||||||
Source vidéo / Player
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="adminAddMovieCollapsePlayer" class="accordion-collapse collapse show" aria-labelledby="adminAddMovieHeadingPlayer" data-bs-parent="#adminAddMovieAccordion">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<div class="row g-2">
|
|
||||||
<div class="col-12 col-lg-4">
|
|
||||||
<label for="player_mode" class="form-label">Mode</label>
|
|
||||||
<select id="player_mode" class="form-select">
|
|
||||||
<option value="auto" selected>Auto</option>
|
|
||||||
<option value="video">Video</option>
|
|
||||||
<option value="embed">Embed</option>
|
|
||||||
</select>
|
|
||||||
<div class="form-text">Auto = video_src → embed_src → vod_link.</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-8">
|
|
||||||
<label for="video_src" class="form-label">Video URL (mp4/webm/m3u8)</label>
|
|
||||||
<input type="text" class="form-control" id="video_src" placeholder="https://.../movie.mp4 or .../playlist.m3u8">
|
|
||||||
<div class="form-text">Si <code>video_file</code> est fourni et que ce champ est vide, la lecture utilisera <code>/api/video/<movieId></code>.</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="video_file" class="form-label">Server video file (optionnel)</label>
|
|
||||||
<input type="text" class="form-control font-monospace" id="video_file" placeholder="my_movie.mp4 or 2026/my_movie.mp4">
|
|
||||||
<div class="form-text">Chemin relatif sous <code>VIDEO_FILES_DIR</code> (défaut: <code>public/video</code>).</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="video_file_low" class="form-label">Server video file (basse qualité, optionnel)</label>
|
|
||||||
<input type="text" class="form-control font-monospace" id="video_file_low" placeholder="LOW/my_movie.LOW.mp4">
|
|
||||||
<div class="form-text">Chemin relatif sous <code>VIDEO_FILES_DIR</code>. Ex: <code>LOW/123.LOW.mp4</code>.</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="subtitle_file" class="form-label">Sous-titres (SRT, optionnel)</label>
|
|
||||||
<input type="file" class="form-control" id="subtitle_file" accept=".srt,.vtt,text/plain,text/vtt">
|
|
||||||
<div class="form-text">Upload d'un fichier <code>.srt</code> (converti en VTT serveur, interne uniquement).</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-4">
|
|
||||||
<label for="subtitle_lang" class="form-label">Langue (ex: fr, en)</label>
|
|
||||||
<input type="text" class="form-control" id="subtitle_lang" placeholder="fr">
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-4">
|
|
||||||
<label for="subtitle_label" class="form-label">Label (affiché)</label>
|
|
||||||
<input type="text" class="form-control" id="subtitle_label" placeholder="Français">
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-4 d-flex align-items-end">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="subtitle_default">
|
|
||||||
<label class="form-check-label" for="subtitle_default">Par défaut</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="embed_src" class="form-label">Embed URL ou iframe</label>
|
|
||||||
<textarea class="form-control font-monospace" id="embed_src" rows="3" placeholder="https://... OR <iframe src='...'></iframe>"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="vod_link" class="form-label">Legacy VOD link (optionnel)</label>
|
|
||||||
<input type="text" class="form-control" id="vod_link" placeholder="https://...">
|
|
||||||
<div class="form-text">Compat. Préfère <code>video_src</code> / <code>embed_src</code>.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 d-flex flex-wrap justify-content-end gap-2">
|
|
||||||
<button type="button" id="reset-form" class="btn btn-outline-secondary">Reset</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Ajouter</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Help -->
|
|
||||||
<div class="tab-pane fade" id="admin-movies-pane-help" role="tabpanel" aria-labelledby="admin-movies-subtab-help" tabindex="0">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-xxl-8">
|
|
||||||
<div class="border rounded p-3 bg-light">
|
|
||||||
<div class="fw-semibold mb-2">Notes rapides</div>
|
|
||||||
<ul class="mb-0">
|
|
||||||
<li><strong>Auto</strong>: utilise <code>video_src</code> → <code>embed_src</code> → <code>vod_link</code>.</li>
|
|
||||||
<li><strong>Server file</strong>: mets <code>video_file</code> (chemin relatif sous <code>public/video</code>).</li>
|
|
||||||
<li><strong>Low quality</strong>: mets <code>video_file_low</code> (ex: <code>LOW/mon_film.LOW.mp4</code>).</li>
|
|
||||||
<li><strong>Édition</strong>: le bouton “Éditer” ouvre une modale avec des onglets Info / Player.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-xxl-4">
|
|
||||||
<div class="border rounded p-3 bg-white">
|
|
||||||
<div class="fw-semibold mb-2">Astuce</div>
|
|
||||||
<div class="text-muted">
|
|
||||||
Dans “Gérer”, la recherche filtre la table localement (sans requête API). Le “Tout sélectionner” ne coche que les lignes visibles.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="admin-backup" role="tabpanel" aria-labelledby="admin-backup-tab">
|
<div class="tab-pane fade" id="admin-backup" role="tabpanel" aria-labelledby="admin-backup-tab">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||||||
<div>
|
<div class="info-line small text-muted">
|
||||||
<h4 class="mb-1">Backup / Restore</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">Sauvegarde et restauration de la base MongoDB.</div>
|
<span class="info-text">Sauvegarde et restauration de la base MongoDB.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -820,8 +716,8 @@
|
|||||||
<div class="text-muted small">Format <code>.ndjson.gz</code>, stocké sur le serveur.</div>
|
<div class="text-muted small">Format <code>.ndjson.gz</code>, stocké sur le serveur.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<button type="button" id="db-backup-create" class="btn btn-warning">Créer une sauvegarde</button>
|
<button type="button" id="db-backup-create" class="btn btn-warning btn-compact">Créer une sauvegarde</button>
|
||||||
<button type="button" id="db-backup-refresh" class="btn btn-outline-secondary">Rafraîchir</button>
|
<button type="button" id="db-backup-refresh" class="btn btn-outline-secondary btn-compact">Rafraîchir</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -869,7 +765,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||||||
<button type="button" id="db-restore-run" class="btn btn-danger">Restaurer</button>
|
<button type="button" id="db-restore-run" class="btn btn-danger btn-compact">Restaurer</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="db-restore-result" class="small text-muted mt-3"></div>
|
<div id="db-restore-result" class="small text-muted mt-3"></div>
|
||||||
@@ -917,9 +813,9 @@
|
|||||||
<div class="tab-pane fade" id="admin-categories" role="tabpanel" aria-labelledby="admin-categories-tab">
|
<div class="tab-pane fade" id="admin-categories" role="tabpanel" aria-labelledby="admin-categories-tab">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||||||
<div>
|
<div class="info-line small text-muted">
|
||||||
<h4 class="mb-1">Catégories Oscars</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">Créez et gérez les catégories manuellement.</div>
|
<span class="info-text">Créez et gérez les catégories manuellement.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1024,9 +920,9 @@
|
|||||||
<div class="tab-pane fade" id="admin-winners" role="tabpanel" aria-labelledby="admin-winners-tab">
|
<div class="tab-pane fade" id="admin-winners" role="tabpanel" aria-labelledby="admin-winners-tab">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2">
|
||||||
<div>
|
<div class="info-line small text-muted">
|
||||||
<h4 class="mb-1">Gagnants</h4>
|
<span class="badge bg-light text-dark border" aria-hidden="true">i</span>
|
||||||
<div class="text-muted">Définis un ou plusieurs gagnants par année (égalité possible).</div>
|
<span class="info-text">Définis un ou plusieurs gagnants par année (égalité possible).</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1075,9 +971,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap justify-content-end gap-2 mt-3">
|
<div class="d-flex flex-wrap justify-content-end gap-2 mt-3">
|
||||||
<button type="button" id="winner_reload" class="btn btn-outline-secondary">Recharger</button>
|
<button type="button" id="winner_reload" class="btn btn-outline-secondary btn-compact">Recharger</button>
|
||||||
<button type="button" id="winner_clear" class="btn btn-outline-danger">Tout supprimer</button>
|
<button type="button" id="winner_clear" class="btn btn-outline-danger btn-compact">Tout supprimer</button>
|
||||||
<button type="button" id="winner_save" class="btn btn-warning">Ajouter</button>
|
<button type="button" id="winner_save" class="btn btn-warning btn-compact">Ajouter</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1156,8 +1052,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Fermer</button>
|
<button type="button" class="btn btn-outline-secondary btn-compact" data-bs-dismiss="modal">Fermer</button>
|
||||||
<button type="button" class="btn btn-danger" id="confirm-reset-user-password">Générer</button>
|
<button type="button" class="btn btn-danger btn-compact" id="confirm-reset-user-password">Générer</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1193,8 +1089,8 @@
|
|||||||
<div id="delete-user-modal-response" class="alert d-none mt-3" role="alert"></div>
|
<div id="delete-user-modal-response" class="alert d-none mt-3" role="alert"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Annuler</button>
|
<button type="button" class="btn btn-outline-secondary btn-compact" data-bs-dismiss="modal">Annuler</button>
|
||||||
<button type="button" class="btn btn-danger" id="confirm-delete-user" disabled>Supprimer</button>
|
<button type="button" class="btn btn-danger btn-compact" id="confirm-delete-user" disabled>Supprimer</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -580,6 +580,75 @@ footer {
|
|||||||
color: #111827;
|
color: #111827;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Admin tabs: visually connect tabs with their content */
|
||||||
|
#adminTabs {
|
||||||
|
border-bottom: 0;
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs .nav-link {
|
||||||
|
background-color: var(--app-surface);
|
||||||
|
border: 1px solid var(--app-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs .nav-link:hover {
|
||||||
|
background-color: var(--app-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs .nav-link.active {
|
||||||
|
background-color: var(--app-surface);
|
||||||
|
border-color: var(--app-border);
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs .nav-link.active::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 1px;
|
||||||
|
right: 1px;
|
||||||
|
bottom: -3px;
|
||||||
|
height: 3px;
|
||||||
|
background: var(--app-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs .nav-link:not(.active) {
|
||||||
|
border-color: var(--app-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs + .admin-tabs-content {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: -1px;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide top border under the active admin tab */
|
||||||
|
#adminTabs + .admin-tabs-content > .tab-pane.active {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs + .admin-tabs-content > .tab-pane.active::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 1px;
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
margin-left: -1px;
|
||||||
|
background: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adminTabs + .admin-tabs-content .tab-pane .card:first-of-type {
|
||||||
|
border-top-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
body.bg-light {
|
body.bg-light {
|
||||||
background-color: var(--app-bg) !important;
|
background-color: var(--app-bg) !important;
|
||||||
@@ -1083,6 +1152,18 @@ footer {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
background: rgba(79, 124, 255, 0.18);
|
background: rgba(79, 124, 255, 0.18);
|
||||||
border-color: rgba(79, 124, 255, 0.4);
|
border-color: rgba(79, 124, 255, 0.4);
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] #adminTabs .nav-link:not(.active) {
|
||||||
|
border-color: var(--app-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] #adminTabs .nav-link.active::after,
|
||||||
|
html[data-theme="dark"] #editMovieTabs .nav-link.active::after {
|
||||||
|
background: var(--app-surface);
|
||||||
|
bottom: -3px;
|
||||||
|
height: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="dark"] #adminTabsContent .table,
|
html[data-theme="dark"] #adminTabsContent .table,
|
||||||
|
|||||||
+52
-93
@@ -1,5 +1,6 @@
|
|||||||
window.onload = async function () {
|
window.onload = async function () {
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
@@ -38,7 +39,7 @@ window.onload = async function () {
|
|||||||
const form = document.getElementById('add-movie-form');
|
const form = document.getElementById('add-movie-form');
|
||||||
const yearInput = document.getElementById('oscar_year');
|
const yearInput = document.getElementById('oscar_year');
|
||||||
|
|
||||||
const activeYearInput = document.getElementById('active_oscar_year');
|
const activeYearInput = document.getElementById('active_oscar_year') || document.getElementById('oscar_date_year');
|
||||||
const saveActiveYearBtn = document.getElementById('save-active-year');
|
const saveActiveYearBtn = document.getElementById('save-active-year');
|
||||||
|
|
||||||
// Player UI (settings tab): show/hide admin status blocks in player.html
|
// Player UI (settings tab): show/hide admin status blocks in player.html
|
||||||
@@ -49,9 +50,6 @@ window.onload = async function () {
|
|||||||
// Version control tab elements
|
// Version control tab elements
|
||||||
const adminVersionTabBtn = document.getElementById('admin-version-tab');
|
const adminVersionTabBtn = document.getElementById('admin-version-tab');
|
||||||
const appVersionAlertEl = document.getElementById('app_version_alert');
|
const appVersionAlertEl = document.getElementById('app_version_alert');
|
||||||
const appVersionActiveSelectEl = document.getElementById('app_version_active_select');
|
|
||||||
const appVersionReloadBtn = document.getElementById('app_version_reload');
|
|
||||||
const appVersionSetActiveBtn = document.getElementById('app_version_set_active');
|
|
||||||
const appVersionPreviewEl = document.getElementById('app_version_preview');
|
const appVersionPreviewEl = document.getElementById('app_version_preview');
|
||||||
const appVersionNewVersionEl = document.getElementById('app_version_new_version');
|
const appVersionNewVersionEl = document.getElementById('app_version_new_version');
|
||||||
const appVersionNewMessageEl = document.getElementById('app_version_new_message');
|
const appVersionNewMessageEl = document.getElementById('app_version_new_message');
|
||||||
@@ -419,40 +417,10 @@ window.onload = async function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderAppVersionUI() {
|
function renderAppVersionUI() {
|
||||||
if (!appVersionActiveSelectEl || !appVersionListEl) return;
|
if (!appVersionListEl) return;
|
||||||
const versions = Array.isArray(appVersionState.versions) ? appVersionState.versions : [];
|
const versions = Array.isArray(appVersionState.versions) ? appVersionState.versions : [];
|
||||||
const activeId = appVersionState?.active?.id ? String(appVersionState.active.id) : '';
|
const activeId = appVersionState?.active?.id ? String(appVersionState.active.id) : '';
|
||||||
|
|
||||||
const previous = String(appVersionActiveSelectEl.value || '');
|
|
||||||
appVersionActiveSelectEl.innerHTML = '';
|
|
||||||
|
|
||||||
if (!versions.length) {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = '';
|
|
||||||
opt.textContent = 'Aucune version (crée-en une)';
|
|
||||||
opt.disabled = true;
|
|
||||||
opt.selected = true;
|
|
||||||
appVersionActiveSelectEl.appendChild(opt);
|
|
||||||
appVersionActiveSelectEl.disabled = true;
|
|
||||||
if (appVersionSetActiveBtn) appVersionSetActiveBtn.disabled = true;
|
|
||||||
} else {
|
|
||||||
versions.forEach((v) => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = String(v?.id || '');
|
|
||||||
const dateLabel = v?.dateISO ? formatDateTime(v.dateISO) : '—';
|
|
||||||
const msg = String(v?.message || '').trim();
|
|
||||||
const shortMsg = msg.length > 80 ? `${msg.slice(0, 77)}...` : msg;
|
|
||||||
opt.textContent = `${v?.version || '—'} — ${dateLabel}${shortMsg ? ` — ${shortMsg}` : ''}`;
|
|
||||||
appVersionActiveSelectEl.appendChild(opt);
|
|
||||||
});
|
|
||||||
|
|
||||||
const stillExists = versions.map((v) => String(v?.id || '')).includes(previous);
|
|
||||||
const desired = activeId || (stillExists ? previous : String(versions[0]?.id || ''));
|
|
||||||
appVersionActiveSelectEl.value = desired;
|
|
||||||
appVersionActiveSelectEl.disabled = false;
|
|
||||||
if (appVersionSetActiveBtn) appVersionSetActiveBtn.disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!versions.length) {
|
if (!versions.length) {
|
||||||
appVersionListEl.textContent = '—';
|
appVersionListEl.textContent = '—';
|
||||||
return;
|
return;
|
||||||
@@ -465,6 +433,9 @@ window.onload = async function () {
|
|||||||
const msg = escapeHtml(String(v?.message || '').trim());
|
const msg = escapeHtml(String(v?.message || '').trim());
|
||||||
const versionLabel = escapeHtml(String(v?.version || '—'));
|
const versionLabel = escapeHtml(String(v?.version || '—'));
|
||||||
const activeBadge = isActive ? '<span class="badge bg-warning text-dark">Active</span>' : '';
|
const activeBadge = isActive ? '<span class="badge bg-warning text-dark">Active</span>' : '';
|
||||||
|
const activateBtn = isActive
|
||||||
|
? ''
|
||||||
|
: `<button type="button" class="btn btn-sm btn-outline-secondary btn-compact btn-check-compact" data-app-version-activate="${escapeHtml(id)}" aria-label="Activer">✓</button>`;
|
||||||
|
|
||||||
// IMPORTANT: keep markup compact (no preserved whitespace surprises).
|
// IMPORTANT: keep markup compact (no preserved whitespace surprises).
|
||||||
return (
|
return (
|
||||||
@@ -476,8 +447,9 @@ window.onload = async function () {
|
|||||||
`</div>` +
|
`</div>` +
|
||||||
`<div class="small text-muted text-break">${escapeHtml(dateLabel)}${msg ? ` — ${msg}` : ''}</div>` +
|
`<div class="small text-muted text-break">${escapeHtml(dateLabel)}${msg ? ` — ${msg}` : ''}</div>` +
|
||||||
`</div>` +
|
`</div>` +
|
||||||
`<div class="flex-shrink-0">` +
|
`<div class="flex-shrink-0 d-flex gap-2">` +
|
||||||
`<button type="button" class="btn btn-sm btn-outline-danger" data-app-version-delete="${escapeHtml(id)}">Supprimer</button>` +
|
`${activateBtn}` +
|
||||||
|
`<button type="button" class="btn btn-sm btn-outline-danger btn-compact" data-app-version-delete="${escapeHtml(id)}">Supprimer</button>` +
|
||||||
`</div>` +
|
`</div>` +
|
||||||
`</div>`
|
`</div>`
|
||||||
);
|
);
|
||||||
@@ -485,6 +457,29 @@ window.onload = async function () {
|
|||||||
|
|
||||||
appVersionListEl.innerHTML = `<div class="list-group list-group-flush">${rows}</div>`;
|
appVersionListEl.innerHTML = `<div class="list-group list-group-flush">${rows}</div>`;
|
||||||
|
|
||||||
|
appVersionListEl.querySelectorAll('button[data-app-version-activate]').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const id = btn.getAttribute('data-app-version-activate');
|
||||||
|
if (!id) return;
|
||||||
|
btn.disabled = true;
|
||||||
|
const old = btn.textContent;
|
||||||
|
btn.textContent = '...';
|
||||||
|
try {
|
||||||
|
hideAppVersionAlert();
|
||||||
|
appVersionState = await setActiveAppVersion(id);
|
||||||
|
appVersionLoadedOnce = true;
|
||||||
|
renderAppVersionUI();
|
||||||
|
await refreshAppVersionPreview();
|
||||||
|
setAppVersionAlert('success', 'Version active mise à jour.');
|
||||||
|
} catch (err) {
|
||||||
|
setAppVersionAlert('danger', err.message || 'Erreur réseau');
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
appVersionListEl.querySelectorAll('button[data-app-version-delete]').forEach((btn) => {
|
appVersionListEl.querySelectorAll('button[data-app-version-delete]').forEach((btn) => {
|
||||||
btn.addEventListener('click', async () => {
|
btn.addEventListener('click', async () => {
|
||||||
const id = btn.getAttribute('data-app-version-delete');
|
const id = btn.getAttribute('data-app-version-delete');
|
||||||
@@ -513,7 +508,7 @@ window.onload = async function () {
|
|||||||
|
|
||||||
async function loadAppVersionTab(options = {}) {
|
async function loadAppVersionTab(options = {}) {
|
||||||
const force = !!options.force;
|
const force = !!options.force;
|
||||||
if (!appVersionActiveSelectEl || !appVersionListEl) return;
|
if (!appVersionListEl) return;
|
||||||
if (appVersionLoadedOnce && !force) return;
|
if (appVersionLoadedOnce && !force) return;
|
||||||
try {
|
try {
|
||||||
hideAppVersionAlert();
|
hideAppVersionAlert();
|
||||||
@@ -1103,7 +1098,7 @@ window.onload = async function () {
|
|||||||
return (
|
return (
|
||||||
`<div class="d-flex justify-content-between align-items-center border rounded px-2 py-1 mb-2 bg-white">` +
|
`<div class="d-flex justify-content-between align-items-center border rounded px-2 py-1 mb-2 bg-white">` +
|
||||||
`<div class="me-2 min-width-0"><strong class="text-break">${name}</strong>${ptsLabel}</div>` +
|
`<div class="me-2 min-width-0"><strong class="text-break">${name}</strong>${ptsLabel}</div>` +
|
||||||
`<button type="button" class="btn btn-sm btn-outline-danger flex-shrink-0" data-winner-remove-year="${escapeHtml(String(y))}" data-winner-remove-user="${uid}">Retirer</button>` +
|
`<button type="button" class="btn btn-sm btn-outline-danger btn-compact flex-shrink-0" data-winner-remove-year="${escapeHtml(String(y))}" data-winner-remove-user="${uid}">Retirer</button>` +
|
||||||
`</div>`
|
`</div>`
|
||||||
);
|
);
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -1235,7 +1230,7 @@ window.onload = async function () {
|
|||||||
return /^tt\d{5,}$/.test(String(value || '').trim());
|
return /^tt\d{5,}$/.test(String(value || '').trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('reset-form').addEventListener('click', function () {
|
document.getElementById('reset-form')?.addEventListener('click', function () {
|
||||||
form.reset();
|
form.reset();
|
||||||
responseEl.classList.add('d-none');
|
responseEl.classList.add('d-none');
|
||||||
});
|
});
|
||||||
@@ -1632,8 +1627,8 @@ window.onload = async function () {
|
|||||||
<td class="text-nowrap">${formatBytes(b?.sizeBytes)}</td>
|
<td class="text-nowrap">${formatBytes(b?.sizeBytes)}</td>
|
||||||
<td class="text-nowrap">${formatDateTime(b?.mtime)}</td>
|
<td class="text-nowrap">${formatDateTime(b?.mtime)}</td>
|
||||||
<td class="text-end text-nowrap">
|
<td class="text-end text-nowrap">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary me-1" data-backup-download="${name}">Télécharger</button>
|
<button type="button" class="btn btn-sm btn-outline-secondary btn-compact me-1" data-backup-download="${name}">Télécharger</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" data-backup-delete="${name}">Supprimer</button>
|
<button type="button" class="btn btn-sm btn-outline-danger btn-compact" data-backup-delete="${name}">Supprimer</button>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
dbBackupListBody.appendChild(tr);
|
dbBackupListBody.appendChild(tr);
|
||||||
@@ -1802,42 +1797,7 @@ window.onload = async function () {
|
|||||||
if (adminVersionTabBtn) {
|
if (adminVersionTabBtn) {
|
||||||
adminVersionTabBtn.addEventListener('shown.bs.tab', () => loadAppVersionTab({ force: false }));
|
adminVersionTabBtn.addEventListener('shown.bs.tab', () => loadAppVersionTab({ force: false }));
|
||||||
}
|
}
|
||||||
if (appVersionReloadBtn) {
|
// Active version is handled inside the history list (✓).
|
||||||
appVersionReloadBtn.addEventListener('click', async () => {
|
|
||||||
appVersionReloadBtn.disabled = true;
|
|
||||||
const old = appVersionReloadBtn.textContent;
|
|
||||||
appVersionReloadBtn.textContent = 'Chargement...';
|
|
||||||
try {
|
|
||||||
await loadAppVersionTab({ force: true });
|
|
||||||
setAppVersionAlert('success', 'Rechargé.');
|
|
||||||
} finally {
|
|
||||||
appVersionReloadBtn.disabled = false;
|
|
||||||
appVersionReloadBtn.textContent = old;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (appVersionSetActiveBtn && appVersionActiveSelectEl) {
|
|
||||||
appVersionSetActiveBtn.addEventListener('click', async () => {
|
|
||||||
const id = String(appVersionActiveSelectEl.value || '').trim();
|
|
||||||
if (!id) return;
|
|
||||||
appVersionSetActiveBtn.disabled = true;
|
|
||||||
const old = appVersionSetActiveBtn.textContent;
|
|
||||||
appVersionSetActiveBtn.textContent = '...';
|
|
||||||
try {
|
|
||||||
hideAppVersionAlert();
|
|
||||||
appVersionState = await setActiveAppVersion(id);
|
|
||||||
appVersionLoadedOnce = true;
|
|
||||||
renderAppVersionUI();
|
|
||||||
await refreshAppVersionPreview();
|
|
||||||
setAppVersionAlert('success', 'Version active mise à jour.');
|
|
||||||
} catch (err) {
|
|
||||||
setAppVersionAlert('danger', err.message || 'Erreur réseau');
|
|
||||||
} finally {
|
|
||||||
appVersionSetActiveBtn.disabled = false;
|
|
||||||
appVersionSetActiveBtn.textContent = old;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCreateAppVersion(activate) {
|
async function handleCreateAppVersion(activate) {
|
||||||
const version = String(appVersionNewVersionEl?.value || '').trim();
|
const version = String(appVersionNewVersionEl?.value || '').trim();
|
||||||
@@ -1990,18 +1950,17 @@ window.onload = async function () {
|
|||||||
const year = parseYear(yearInput.value);
|
const year = parseYear(yearInput.value);
|
||||||
const imdb_id = document.getElementById('imdb_id').value.trim();
|
const imdb_id = document.getElementById('imdb_id').value.trim();
|
||||||
const category = document.getElementById('category').value.trim();
|
const category = document.getElementById('category').value.trim();
|
||||||
const cam = !!document.getElementById('cam_flag')?.checked;
|
const cam = false;
|
||||||
const vod_link = document.getElementById('vod_link').value.trim();
|
const vod_link = '';
|
||||||
const player_mode = (document.getElementById('player_mode')?.value || 'auto').trim();
|
const player_mode = 'auto';
|
||||||
const video_src = document.getElementById('video_src')?.value?.trim() || '';
|
const video_src = '';
|
||||||
const video_file = document.getElementById('video_file')?.value?.trim() || '';
|
const video_file = '';
|
||||||
const video_file_low = document.getElementById('video_file_low')?.value?.trim() || '';
|
const video_file_low = '';
|
||||||
const embed_src = document.getElementById('embed_src')?.value?.trim() || '';
|
const embed_src = '';
|
||||||
const subtitleFileInput = document.getElementById('subtitle_file');
|
const subtitleFile = null;
|
||||||
const subtitleFile = subtitleFileInput?.files?.[0] || null;
|
const subtitle_lang = '';
|
||||||
const subtitle_lang = normalizeSubtitleText(document.getElementById('subtitle_lang')?.value, 24);
|
const subtitle_label = '';
|
||||||
const subtitle_label = normalizeSubtitleText(document.getElementById('subtitle_label')?.value, 80);
|
const subtitle_default = false;
|
||||||
const subtitle_default = !!document.getElementById('subtitle_default')?.checked;
|
|
||||||
|
|
||||||
if (!year) {
|
if (!year) {
|
||||||
showResponse('warning', 'Année invalide. Exemple attendu: 2026');
|
showResponse('warning', 'Année invalide. Exemple attendu: 2026');
|
||||||
@@ -2158,7 +2117,7 @@ window.onload = async function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Oscar date year dropdown (settings tab)
|
// Oscar date year dropdown (settings tab)
|
||||||
if (oscarDateYearSelect && oscarDateValueInput && saveOscarDateBtn && clearOscarDateBtn) {
|
if (oscarDateYearSelect && oscarDateValueInput && saveOscarDateBtn) {
|
||||||
const previousOscarYearSelection = String(oscarDateYearSelect.value || '');
|
const previousOscarYearSelection = String(oscarDateYearSelect.value || '');
|
||||||
oscarDateYearSelect.innerHTML = '';
|
oscarDateYearSelect.innerHTML = '';
|
||||||
|
|
||||||
@@ -2172,7 +2131,7 @@ window.onload = async function () {
|
|||||||
oscarDateYearSelect.disabled = true;
|
oscarDateYearSelect.disabled = true;
|
||||||
oscarDateValueInput.disabled = true;
|
oscarDateValueInput.disabled = true;
|
||||||
saveOscarDateBtn.disabled = true;
|
saveOscarDateBtn.disabled = true;
|
||||||
clearOscarDateBtn.disabled = true;
|
if (clearOscarDateBtn) clearOscarDateBtn.disabled = true;
|
||||||
} else {
|
} else {
|
||||||
cleanedYears.forEach((y) => {
|
cleanedYears.forEach((y) => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
@@ -2191,7 +2150,7 @@ window.onload = async function () {
|
|||||||
oscarDateYearSelect.disabled = false;
|
oscarDateYearSelect.disabled = false;
|
||||||
oscarDateValueInput.disabled = false;
|
oscarDateValueInput.disabled = false;
|
||||||
saveOscarDateBtn.disabled = false;
|
saveOscarDateBtn.disabled = false;
|
||||||
clearOscarDateBtn.disabled = false;
|
if (clearOscarDateBtn) clearOscarDateBtn.disabled = false;
|
||||||
oscarDateYearSelect.value = desired;
|
oscarDateYearSelect.value = desired;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -35,9 +35,23 @@
|
|||||||
.stats-tabs .nav-link {
|
.stats-tabs .nav-link {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
.stats-tabs {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
.stats-tab-panel {
|
.stats-tab-panel {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.stats-tab-panel > .tab-pane:not(#pane-ranking) {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
|
#pane-ranking .user-table {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
.stats-tab-panel .table,
|
||||||
|
.stats-tab-panel .table th,
|
||||||
|
.stats-tab-panel .table td {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light page-loading">
|
<body class="bg-light page-loading">
|
||||||
@@ -111,7 +125,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="tab-winners" data-bs-toggle="tab" data-bs-target="#pane-winners" type="button" role="tab" aria-controls="pane-winners" aria-selected="false">
|
<button class="nav-link" id="tab-winners" data-bs-toggle="tab" data-bs-target="#pane-winners" type="button" role="tab" aria-controls="pane-winners" aria-selected="false">
|
||||||
Gagnants
|
Gagnants 🏆
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
|
|||||||
Reference in New Issue
Block a user