Initial commit
This commit is contained in:
124
resources/js/Pages/Admin/Comparative.vue
Normal file
124
resources/js/Pages/Admin/Comparative.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<script setup>
|
||||
import AdminLayout from '@/Layouts/AdminLayout.vue';
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
candidates: Array
|
||||
});
|
||||
|
||||
const searchQuery = ref('');
|
||||
|
||||
const filteredCandidates = computed(() => {
|
||||
return props.candidates.filter(c =>
|
||||
c.user.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
c.user.email.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
).sort((a, b) => {
|
||||
const scoreA = a.attempts[0]?.score || 0;
|
||||
const scoreB = b.attempts[0]?.score || 0;
|
||||
return scoreB - scoreA;
|
||||
});
|
||||
});
|
||||
|
||||
const getScorePercentage = (candidate) => {
|
||||
const attempt = candidate.attempts[0];
|
||||
if (!attempt || !attempt.quiz) return 0;
|
||||
|
||||
// Total points possible (this would ideally be passed from backend or calculated)
|
||||
// For now, we assume attempt.score is the absolute score.
|
||||
return attempt.score;
|
||||
};
|
||||
|
||||
const formatDuration = (started, finished) => {
|
||||
if (!started || !finished) return '--';
|
||||
const start = new Date(started);
|
||||
const end = new Date(finished);
|
||||
const diff = Math.floor((end - start) / 1000 / 60);
|
||||
return diff + ' min';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head title="Comparateur de Candidats" />
|
||||
|
||||
<AdminLayout>
|
||||
<template #header>
|
||||
Comparateur de Candidats
|
||||
</template>
|
||||
|
||||
<div class="mb-8 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<h3 class="text-2xl font-bold">Classement par Score</h3>
|
||||
|
||||
<div class="relative w-full sm:w-64">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-slate-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Rechercher..."
|
||||
class="block w-full pl-10 pr-3 py-2 border border-slate-300 dark:border-slate-700 rounded-lg leading-5 bg-white dark:bg-slate-800 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm transition-all"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||
<table class="w-full text-left">
|
||||
<thead class="bg-slate-50 dark:bg-slate-700/50 border-b border-slate-200 dark:border-slate-700">
|
||||
<tr>
|
||||
<th class="px-6 py-4 font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-xs">Rang</th>
|
||||
<th class="px-6 py-4 font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-xs">Candidat</th>
|
||||
<th class="px-6 py-4 font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-xs text-center">Score</th>
|
||||
<th class="px-6 py-4 font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-xs">Temps passé</th>
|
||||
<th class="px-6 py-4 font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-xs">Test</th>
|
||||
<th class="px-6 py-4 font-bold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-xs text-right">Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
|
||||
<tr v-for="(candidate, index) in filteredCandidates" :key="candidate.id" class="hover:bg-slate-50/50 dark:hover:bg-slate-700/30 transition-colors group">
|
||||
<td class="px-6 py-4">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm"
|
||||
:class="[
|
||||
index === 0 ? 'bg-yellow-100 text-yellow-700' :
|
||||
index === 1 ? 'bg-slate-200 text-slate-700' :
|
||||
index === 2 ? 'bg-orange-100 text-orange-700' : 'text-slate-500'
|
||||
]"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="font-bold text-slate-900 dark:text-slate-100">{{ candidate.user.name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ candidate.user.email }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="text-xl font-black text-indigo-600 dark:text-indigo-400">{{ candidate.attempts[0]?.score }}</span>
|
||||
<span class="text-[10px] uppercase font-bold text-slate-400">Points</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-slate-600 dark:text-slate-400 font-medium">
|
||||
{{ formatDuration(candidate.attempts[0]?.started_at, candidate.attempts[0]?.finished_at) }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="text-sm px-2 py-1 bg-slate-100 dark:bg-slate-700 rounded-md font-medium">
|
||||
{{ candidate.attempts[0]?.quiz?.title }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right text-slate-500 text-sm">
|
||||
{{ new Date(candidate.attempts[0]?.finished_at).toLocaleDateString() }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="filteredCandidates.length === 0">
|
||||
<td colspan="6" class="px-6 py-12 text-center text-slate-500 italic">
|
||||
Aucun candidat n'a encore terminé de test.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
Reference in New Issue
Block a user