{% extends 'base.html.twig' %}
{# Les nouveaux en premier #}
{% set translations = translations|sort((a, b) => (a.redirectUrl == redirectUrl ? -1 : 1) <=> (b.redirectUrl == redirectUrl ? -1 : 1)) %}
{% set locales_icons = {
'FR_FR': '🇫🇷',
'EN_GB': '🇬🇧',
'ES_ES': '🇪🇸',
'DE_DE': '🇩🇪',
'PT_PT': '🇵🇹',
'IT_IT': '🇮🇹',
'NL_NL': '🇳🇱',
'PL_PL': '🇵🇱',
'RU_RU': '🇷🇺',
'ZH_ZH': '🇨🇳',
'JA_JA': '🇯🇵',
'AR_AR': '🇸🇦'
} %}
{% set locales_names = {
'FR_FR': 'Français',
'EN_GB': 'Anglais',
'ES_ES': 'Espagnol',
'DE_DE': 'Allemand',
'PT_PT': 'Portugais',
'IT_IT': 'Italien',
'NL_NL': 'Néerlandais',
'PL_PL': 'Polonais',
'RU_RU': 'Russe',
'ZH_ZH': 'Chinois',
'JA_JA': 'Japonais',
'AR_AR': 'Arabe'
} %}
{% block title %}Modération des Traductions{% endblock %}
{% block body %}
<style>
.same-width-col {
width: 35%; /* ou 30%, selon ton besoin */
min-width: 200px; /* facultatif, pour les petits écrans */
}
td.source-cell,
td.translation-cell {
vertical-align: middle;
}
td.translation-cell {
display: flex;
align-items: center;
}
td.translation-cell .translation-input {
flex: 1;
}
</style>
<div class="container mt-5">
<h2 class="mb-4 text-center">Modération des Traductions</h2>
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">Traductions en attente de validation</h5>
<div class="form-check mb-3 d-none">
<input class="form-check-input" type="checkbox" value="" id="showAll" checked>
<label class="form-check-label" for="showAll">
Afficher toutes les traductions en attente
</label>
</div>
</div>
<div class="card-body">
{% if translations|length > 0 %}
<div class="d-flex justify-content-end gap-2 mb-2">
<input type="text" id="search-input" class="form-control form-control-sm w-auto mr-2"
placeholder="🔍 Rechercher dans texte source..." />
<button type="button"
class="btn btn-outline-success mr-2 btn-sm filter-toggle active"
data-status="nouveau">
Nouveaux :
{{ translations|filter(t => t.redirectUrl == redirectUrl)|length }}
</button>
<button type="button"
class="btn btn-outline-warning btn-sm filter-toggle active"
data-status="attente">
En attente :
{{ translations|filter(t => t.redirectUrl != redirectUrl)|length }}
</button>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Statut</th>
<th>Langue</th>
<th class="same-width-col">Texte Source</th>
<th class="same-width-col">Traduction Proposée</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for translation in translations %}
<tr class="translation-row" data-status="{{ translation.redirectUrl == redirectUrl ? 'new' : 'pending' }}">
<td>{{ loop.index }}</td>
<td class="text-center statut-cell">
<span class="badge badge-status
bg-{% if translation.redirectUrl == redirectUrl %}success{% else %}warning{% endif %}">
{% if translation.redirectUrl == redirectUrl %}
✔️
{% else %}
⏳
{% endif %}
</span>
</td>
<td>
<span class="badge bg-light text-dark border">
{{ locales_icons[translation.locale|upper] ?? '' }}
{{ locales_names[translation.locale|upper] ?? translation.locale|upper }}
</span>
</td>
<td class="source-cell">{{ translation.keyName }}</td>
<td>
<input type="text" class="form-control translation-input" value="{{ translation.translatedText }}" />
</td>
<td class="translation2-cell">
<button style="padding: 2px !important;" type="button"
class="btn btn-sm btn-outline-secondary copy-source"
title="Copier le texte source"
data-source="{{ translation.keyName }}">
<i class="la la-refresh"></i>
</button>
<button style="padding: 2px !important;" class="btn btn-sm btn-success validate-translation" data-id="{{ translation.id }}" title="Valider la traduction">
<i class="la la-check"></i>
</button>
<button style="padding: 2px !important;" class="btn btn-sm btn-danger reject-translation" data-id="{{ translation.id }}" title="Rejeter la traduction">
<i class="la la-eraser"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center">
Aucune traduction en attente de validation.
</div>
{% endif %}
{% if redirectUrl != "/symfony/public/" %}
<div class="text-center mt-3">
<a href="{{ redirectUrl }}" class="btn btn-primary">Retour à la page précédente</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".validate-translation").forEach(button => {
button.addEventListener("click", function () {
let row = this.closest("tr");
let translationId = this.dataset.id;
let newTranslation = row.querySelector(".translation-input").value;
fetch(`/symfony/public/moderation/translation/validate/${translationId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
},
body: JSON.stringify({ translation: newTranslation, redirectUrl: "{{ redirectUrl }}" })
})
.then(response => response.json())
.then(data => {
if (data.status === "success") {
//row.querySelector(".status-badge").classList.replace("bg-warning", "bg-success");
//row.querySelector(".status-badge").textContent = "Validée";
row.remove(); // Supprime la ligne du tableau
}
});
});
});
document.querySelectorAll(".reject-translation").forEach(button => {
button.addEventListener("click", function () {
let row = this.closest("tr");
let translationId = this.dataset.id;
fetch(`/symfony/public/moderation/translation/reject/${translationId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
}
})
.then(response => response.json())
.then(data => {
if (data.status === "success") {
row.remove(); // Supprime la ligne du tableau
}
});
});
});
});
</script>
<script>
document.getElementById('showAll').addEventListener('change', function () {
const showAll = this.checked;
document.querySelectorAll('.translation-row').forEach(function (row) {
const isNew = row.dataset.status === 'new';
if (showAll || isNew) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
// Facultatif : déclencher une fois au chargement
document.getElementById('showAll').dispatchEvent(new Event('change'));
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const rows = document.querySelectorAll('tbody tr');
const buttons = document.querySelectorAll('.filter-toggle');
const redirectUrl = '{{ redirectUrl }}';
// Renvoie les statuts actifs sélectionnés
function getActiveStatuses() {
return Array.from(buttons)
.filter(btn => btn.classList.contains('active'))
.map(btn => btn.dataset.status);
}
// Met à jour la visibilité des lignes selon les statuts actifs
function filterRows() {
const activeStatuses = getActiveStatuses();
rows.forEach(row => {
const isNew = row.querySelector('td:nth-child(5) .badge')?.classList.contains('bg-success');
const status = isNew ? 'nouveau' : 'attente';
row.style.display = activeStatuses.includes(status) ? '' : 'none';
});
}
// Toggle bouton + mise à jour affichage
buttons.forEach(btn => {
btn.addEventListener('click', () => {
btn.classList.toggle('active');
filterRows();
});
});
// Filtrage initial
filterRows();
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.copy-source').forEach(button => {
button.addEventListener('click', function () {
const row = this.closest('tr');
const sourceText = this.dataset.source;
const input = row.querySelector('.translation-input');
if (input) {
input.value = sourceText;
input.classList.add('border-warning');
setTimeout(() => input.classList.remove('border-warning'), 1000);
}
});
});
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const searchInput = document.getElementById('search-input');
const rows = document.querySelectorAll('tbody tr');
searchInput.addEventListener('input', function () {
const filter = this.value.trim().toLowerCase();
rows.forEach(row => {
const sourceText = row.querySelector('td.source-cell')?.textContent.toLowerCase() || '';
if (sourceText.includes(filter)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
});
</script>
{% endblock %}