Files
PatoisSMEH/src/views/Texts.vue

312 lines
10 KiB
Vue

<template>
<div class="min-h-screen bg-white py-8">
<div class="max-w-6xl mx-auto px-4">
<!-- En-tête avec recherche -->
<div class="mb-8">
<h1 class="text-4xl font-bold text-black mb-4">Collection de Textes</h1>
<p class="text-gray-600 mb-6">
Explorez notre collection de textes en patois franco-provençal
</p>
<!-- Barre de recherche avancée -->
<div class="bg-gray-50 rounded-lg p-6 mb-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<input
v-model="searchQuery"
@input="performSearch"
type="text"
placeholder="Rechercher dans les textes..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-black"
/>
</div>
<div>
<select
v-model="selectedCategory"
@change="performSearch"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-black"
>
<option value="">Toutes les catégories</option>
<option v-for="category in categories" :key="category" :value="category">
{{ category }}
</option>
</select>
</div>
<div>
<select
v-model="selectedDifficulty"
@change="performSearch"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-black"
>
<option value="">Toutes les difficultés</option>
<option value="Facile">Facile</option>
<option value="Moyen">Moyen</option>
<option value="Difficile">Difficile</option>
</select>
</div>
</div>
<!-- Filtres supplémentaires -->
<div class="flex flex-wrap gap-4 mt-4">
<label class="flex items-center">
<input
v-model="onlyWithAudio"
@change="performSearch"
type="checkbox"
class="rounded mr-2"
/>
<span class="text-sm">Seulement avec audio</span>
</label>
<button
@click="clearFilters"
class="text-sm text-gray-600 hover:text-black"
>
Effacer les filtres
</button>
</div>
</div>
</div>
<!-- Résultats -->
<div class="mb-6">
<p style="color: #6b7280" class="mb-6">
{{ filteredTexts.length }} texte(s) trouvé(s)
<span v-if="searchQuery || selectedCategory || selectedDifficulty || onlyWithAudio">
pour vos critères de recherche
</span>
</p>
</div>
<!-- Liste des textes -->
<div class="grid gap-6">
<div
v-for="text in paginatedTexts"
:key="text.id"
class="bg-white border border-gray-200 rounded-lg p-6 hover:shadow-md transition-shadow cursor-pointer"
@click="$router.push(`/texte/${text.id}`)"
>
<div class="flex justify-between items-start">
<div class="flex-1">
<h2 class="text-xl font-semibold text-black mb-2">
{{ text.metadata.titre_fr }}
</h2>
<p class="text-lg mb-3" style="color: #6b7280">
{{ text.metadata.titre_pt }}
</p>
<div class="flex flex-wrap gap-4 text-sm mb-4" style="color: #6b7280">
<span v-if="text.metadata.auteur">
<strong>Auteur:</strong> {{ text.metadata.auteur }}
</span>
<span v-if="text.metadata.traducteur">
<strong>Traducteur:</strong> {{ text.metadata.traducteur }}
</span>
<span v-if="text.metadata.categorie">
<strong>Catégorie:</strong> {{ text.metadata.categorie }}
</span>
</div>
<!-- Aperçu du texte -->
<p class="text-sm line-clamp-3" style="color: #6b7280">
{{ getTextPreview(text.frenchText) }}
</p>
</div>
<div class="flex flex-col items-end ml-4">
<!-- Badges -->
<div class="flex flex-col gap-2">
<span
v-if="text.metadata.difficulte"
class="px-2 py-1 rounded-full text-xs"
:class="getDifficultyClass(text.metadata.difficulte)"
>
{{ text.metadata.difficulte }}
</span>
<span v-if="text.hasAudio" class="flex items-center text-xs" style="color: #6b7280">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v6.114A4.369 4.369 0 005 11c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.369 4.369 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z"/>
</svg>
Audio
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<div v-if="totalPages > 1" class="mt-8 flex justify-center">
<div class="flex space-x-2">
<button
@click="currentPage = Math.max(1, currentPage - 1)"
:disabled="currentPage === 1"
class="px-4 py-2 border border-gray-300 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
Précédent
</button>
<span class="px-4 py-2 bg-black text-white rounded-lg">
{{ currentPage }} / {{ totalPages }}
</span>
<button
@click="currentPage = Math.min(totalPages, currentPage + 1)"
:disabled="currentPage === totalPages"
class="px-4 py-2 border border-gray-300 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
Suivant
</button>
</div>
</div>
<!-- Message si aucun résultat -->
<div v-if="filteredTexts.length === 0" class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h3 class="text-lg font-medium text-gray-900 mb-2">Aucun texte trouvé</h3>
<p class="text-gray-500">Essayez de modifier vos critères de recherche.</p>
</div>
</div>
</div>
</template>
<script>
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { textService } from '../services/textService.js'
export default {
name: 'Texts',
setup() {
const route = useRoute()
const allTexts = ref([])
const searchQuery = ref('')
const selectedCategory = ref('')
const selectedDifficulty = ref('')
const onlyWithAudio = ref(false)
const currentPage = ref(1)
const textsPerPage = 10
const categories = computed(() => {
const cats = new Set()
allTexts.value.forEach(text => {
if (text.metadata.categorie) {
cats.add(text.metadata.categorie)
}
})
return Array.from(cats).sort()
})
const filteredTexts = computed(() => {
// Ne pas filtrer localement - l'API backend s'en charge
return allTexts.value
})
const totalPages = computed(() => {
return Math.ceil(filteredTexts.value.length / textsPerPage)
})
const paginatedTexts = computed(() => {
const start = (currentPage.value - 1) * textsPerPage
const end = start + textsPerPage
return filteredTexts.value.slice(start, end)
})
const loadTexts = async () => {
try {
// Utiliser l'API de recherche avec les filtres actuels
const texts = await textService.searchTexts(searchQuery.value, {
category: selectedCategory.value,
difficulty: selectedDifficulty.value,
onlyWithAudio: onlyWithAudio.value
})
allTexts.value = texts
} catch (error) {
console.error('Erreur lors du chargement des textes:', error)
// Fallback : charger tous les textes si la recherche échoue
try {
const allTextsData = await textService.loadAllTexts()
allTexts.value = allTextsData
} catch (fallbackError) {
console.error('Erreur fallback:', fallbackError)
allTexts.value = []
}
}
}
const performSearch = async () => {
currentPage.value = 1
await loadTexts()
}
const clearFilters = async () => {
searchQuery.value = ''
selectedCategory.value = ''
selectedDifficulty.value = ''
onlyWithAudio.value = false
currentPage.value = 1
// IMPORTANT: Recharger via l'API après avoir vidé les filtres
await loadTexts()
}
const getTextPreview = (text) => {
if (!text) return ''
return text.split('\n').slice(0, 3).join(' ').substring(0, 200) + '...'
}
const getDifficultyClass = (difficulty) => {
switch (difficulty?.toLowerCase()) {
case 'facile':
return 'bg-green-100 text-green-800'
case 'moyen':
case 'moyenne':
return 'bg-yellow-100 text-yellow-800'
case 'difficile':
return 'bg-red-100 text-red-800'
default:
return 'bg-gray-100 text-gray-800'
}
}
// Initialiser la recherche depuis l'URL
watch(() => route.query.search, (newSearch) => {
if (newSearch) {
searchQuery.value = newSearch
}
}, { immediate: true })
onMounted(() => {
loadTexts()
})
return {
allTexts,
searchQuery,
selectedCategory,
selectedDifficulty,
onlyWithAudio,
currentPage,
categories,
filteredTexts,
totalPages,
paginatedTexts,
performSearch,
clearFilters,
getTextPreview,
getDifficultyClass
}
}
}
</script>
<style scoped>
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>