Ajout du score global et du tri dynamique dans la liste des candidats
This commit is contained in:
@@ -15,7 +15,7 @@ class CandidateController extends Controller
|
|||||||
{
|
{
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$candidates = Candidate::with(['user', 'documents'])->latest()->get();
|
$candidates = Candidate::with(['user', 'documents', 'attempts'])->latest()->get();
|
||||||
|
|
||||||
return \Inertia\Inertia::render('Admin/Candidates/Index', [
|
return \Inertia\Inertia::render('Admin/Candidates/Index', [
|
||||||
'candidates' => $candidates
|
'candidates' => $candidates
|
||||||
|
|||||||
@@ -46,6 +46,37 @@ const deleteCandidate = (id) => {
|
|||||||
const openPreview = (doc) => {
|
const openPreview = (doc) => {
|
||||||
selectedDocument.value = doc;
|
selectedDocument.value = doc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sorting Logic
|
||||||
|
const sortKey = ref('user.name');
|
||||||
|
const sortOrder = ref(1); // 1 = asc, -1 = desc
|
||||||
|
|
||||||
|
const sortBy = (key) => {
|
||||||
|
if (sortKey.value === key) {
|
||||||
|
sortOrder.value *= -1;
|
||||||
|
} else {
|
||||||
|
sortKey.value = key;
|
||||||
|
sortOrder.value = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNestedValue = (obj, path) => {
|
||||||
|
return path.split('.').reduce((o, i) => (o ? o[i] : null), obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedCandidates = computed(() => {
|
||||||
|
return [...props.candidates].sort((a, b) => {
|
||||||
|
let valA = getNestedValue(a, sortKey.value);
|
||||||
|
let valB = getNestedValue(b, sortKey.value);
|
||||||
|
|
||||||
|
if (typeof valA === 'string') valA = valA.toLowerCase();
|
||||||
|
if (typeof valB === 'string') valB = valB.toLowerCase();
|
||||||
|
|
||||||
|
if (valA < valB) return -1 * sortOrder.value;
|
||||||
|
if (valA > valB) return 1 * sortOrder.value;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -81,34 +112,85 @@ const openPreview = (doc) => {
|
|||||||
<table class="w-full text-left">
|
<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">
|
<thead class="bg-slate-50 dark:bg-slate-700/50 border-b border-slate-200 dark:border-slate-700">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300">Nom</th>
|
<th @click="sortBy('user.name')" class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300 cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700/50 transition-colors">
|
||||||
<th class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300">Email</th>
|
<div class="flex items-center gap-2">
|
||||||
<th class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300">Statut</th>
|
Nom
|
||||||
|
<svg v-show="sortKey === 'user.name'" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" :class="{ 'rotate-180': sortOrder === -1 }" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th @click="sortBy('user.email')" class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300 cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700/50 transition-colors">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
Email
|
||||||
|
<svg v-show="sortKey === 'user.email'" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" :class="{ 'rotate-180': sortOrder === -1 }" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th @click="sortBy('status')" class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300 cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700/50 transition-colors">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
Statut
|
||||||
|
<svg v-show="sortKey === 'status'" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" :class="{ 'rotate-180': sortOrder === -1 }" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th @click="sortBy('weighted_score')" class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300 cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700/50 transition-colors">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
Score /20
|
||||||
|
<svg v-show="sortKey === 'weighted_score'" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" :class="{ 'rotate-180': sortOrder === -1 }" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300">Documents</th>
|
<th class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300">Documents</th>
|
||||||
<th class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300 text-right">Actions</th>
|
<th class="px-6 py-4 font-semibold text-slate-700 dark:text-slate-300 text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
|
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
|
||||||
<tr v-for="candidate in candidates" :key="candidate.id" class="hover:bg-slate-50/50 dark:hover:bg-slate-700/30 transition-colors">
|
<tr v-for="candidate in sortedCandidates" :key="candidate.id" class="hover:bg-slate-50/50 dark:hover:bg-slate-700/30 transition-colors">
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
<div class="font-medium">{{ candidate.user.name }}</div>
|
<div class="font-bold text-slate-900 dark:text-white">{{ candidate.user.name }}</div>
|
||||||
<div class="text-xs text-slate-500">{{ candidate.phone }}</div>
|
<div class="text-[10px] text-slate-500 font-bold uppercase tracking-tight">{{ candidate.phone }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 text-slate-600 dark:text-slate-400">
|
<td class="px-6 py-4 text-sm text-slate-600 dark:text-slate-400">
|
||||||
{{ candidate.user.email }}
|
{{ candidate.user.email }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4 text-xs font-bold uppercase tracking-widest">
|
||||||
<span
|
<span
|
||||||
class="px-2 py-1 rounded-full text-xs font-semibold"
|
class="px-3 py-1 rounded-lg"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400': candidate.status === 'en_attente',
|
'bg-amber-50 text-amber-700 border border-amber-200 dark:bg-amber-900/20 dark:border-amber-800 dark:text-amber-400': candidate.status === 'en_attente',
|
||||||
'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400': candidate.status === 'en_cours',
|
'bg-indigo-50 text-indigo-700 border border-indigo-200 dark:bg-indigo-900/20 dark:border-indigo-800 dark:text-indigo-400': candidate.status === 'en_cours',
|
||||||
'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400': candidate.status === 'termine'
|
'bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-900/20 dark:border-emerald-800 dark:text-emerald-400': candidate.status === 'termine'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ candidate.status }}
|
{{ candidate.status }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-12 h-1.5 bg-slate-100 dark:bg-slate-700 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
class="h-full rounded-full transition-all duration-500"
|
||||||
|
:style="{ width: (candidate.weighted_score / 20) * 100 + '%' }"
|
||||||
|
:class="{
|
||||||
|
'bg-emerald-500': candidate.weighted_score >= 14,
|
||||||
|
'bg-amber-500': candidate.weighted_score >= 10 && candidate.weighted_score < 14,
|
||||||
|
'bg-rose-500': candidate.weighted_score < 10
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<span class="font-black text-sm" :class="{
|
||||||
|
'text-emerald-600': candidate.weighted_score >= 14,
|
||||||
|
'text-amber-600': candidate.weighted_score >= 10 && candidate.weighted_score < 14,
|
||||||
|
'text-rose-600': candidate.weighted_score < 10
|
||||||
|
}">
|
||||||
|
{{ candidate.weighted_score }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user