Admin: implementation of the 'is_selected' feature for candidates for interviews
This commit is contained in:
@@ -237,6 +237,15 @@ class CandidateController extends Controller
|
|||||||
$this->storeDocument($candidate, $file, $type);
|
$this->storeDocument($candidate, $file, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleSelection(Candidate $candidate)
|
||||||
|
{
|
||||||
|
$candidate->update([
|
||||||
|
'is_selected' => !$candidate->is_selected
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('success', 'Statut de sélection mis à jour.');
|
||||||
|
}
|
||||||
|
|
||||||
private function storeDocument(Candidate $candidate, $file, string $type)
|
private function storeDocument(Candidate $candidate, $file, string $type)
|
||||||
{
|
{
|
||||||
if (!$file) {
|
if (!$file) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
|
|
||||||
use App\Traits\BelongsToTenant;
|
use App\Traits\BelongsToTenant;
|
||||||
|
|
||||||
#[Fillable(['user_id', 'job_position_id', 'phone', 'linkedin_url', 'status', 'notes', 'cv_score', 'motivation_score', 'interview_score', 'ai_analysis', 'tenant_id'])]
|
#[Fillable(['user_id', 'job_position_id', 'phone', 'linkedin_url', 'status', 'is_selected', 'notes', 'cv_score', 'motivation_score', 'interview_score', 'ai_analysis', 'tenant_id'])]
|
||||||
class Candidate extends Model
|
class Candidate extends Model
|
||||||
{
|
{
|
||||||
use HasFactory, BelongsToTenant;
|
use HasFactory, BelongsToTenant;
|
||||||
@@ -30,6 +30,7 @@ class Candidate extends Model
|
|||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'ai_analysis' => 'array',
|
'ai_analysis' => 'array',
|
||||||
|
'is_selected' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function jobPosition(): BelongsTo
|
public function jobPosition(): BelongsTo
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('candidates', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_selected')->default(false)->after('status');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('candidates', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_selected');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -43,10 +43,14 @@ const submit = () => {
|
|||||||
|
|
||||||
const deleteCandidate = (id) => {
|
const deleteCandidate = (id) => {
|
||||||
if (confirm('Voulez-vous vraiment supprimer ce candidat ?')) {
|
if (confirm('Voulez-vous vraiment supprimer ce candidat ?')) {
|
||||||
router.delete(route('admin.candidates.destroy', id));
|
router.delete(route('admin.candidates.destroy', id), { preserveScroll: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleSelection = (id) => {
|
||||||
|
router.patch(route('admin.candidates.toggle-selection', id), {}, { preserveScroll: true });
|
||||||
|
};
|
||||||
|
|
||||||
const openPreview = (doc) => {
|
const openPreview = (doc) => {
|
||||||
selectedDocument.value = doc;
|
selectedDocument.value = doc;
|
||||||
};
|
};
|
||||||
@@ -69,11 +73,24 @@ const getNestedValue = (obj, path) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectedJobPosition = ref('');
|
const selectedJobPosition = ref('');
|
||||||
|
const showOnlySelected = ref(false);
|
||||||
|
|
||||||
const filteredCandidates = computed(() => {
|
const filteredCandidates = computed(() => {
|
||||||
if (selectedJobPosition.value === '') return props.candidates;
|
let result = props.candidates;
|
||||||
if (selectedJobPosition.value === 'none') return props.candidates.filter(c => !c.job_position_id);
|
|
||||||
return props.candidates.filter(c => c.job_position_id === selectedJobPosition.value);
|
if (showOnlySelected.value) {
|
||||||
|
result = result.filter(c => c.is_selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedJobPosition.value !== '') {
|
||||||
|
if (selectedJobPosition.value === 'none') {
|
||||||
|
result = result.filter(c => !c.job_position_id);
|
||||||
|
} else {
|
||||||
|
result = result.filter(c => c.job_position_id === selectedJobPosition.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedCandidates = computed(() => {
|
const sortedCandidates = computed(() => {
|
||||||
@@ -102,6 +119,13 @@ const sortedCandidates = computed(() => {
|
|||||||
<div class="flex justify-between items-end mb-8">
|
<div class="flex justify-between items-end mb-8">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-2xl font-bold">Liste des Candidats</h3>
|
<h3 class="text-2xl font-bold">Liste des Candidats</h3>
|
||||||
|
<div class="flex items-center gap-6">
|
||||||
|
<div class="flex items-center gap-3 bg-white dark:bg-slate-800 p-2 rounded-xl border border-slate-200 dark:border-slate-700 shadow-sm">
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer px-2">
|
||||||
|
<input type="checkbox" v-model="showOnlySelected" class="rounded border-amber-300 text-amber-500 focus:ring-amber-500/20 cursor-pointer">
|
||||||
|
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">Retenus uniquement</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">Filtrer par fiche de poste :</label>
|
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">Filtrer par fiche de poste :</label>
|
||||||
<select
|
<select
|
||||||
@@ -116,6 +140,7 @@ const sortedCandidates = computed(() => {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<PrimaryButton @click="isModalOpen = true">
|
<PrimaryButton @click="isModalOpen = true">
|
||||||
Ajouter un Candidat
|
Ajouter un Candidat
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
@@ -139,6 +164,7 @@ const sortedCandidates = computed(() => {
|
|||||||
<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="w-12 px-6 py-4"></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 @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">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
Nom
|
Nom
|
||||||
@@ -201,6 +227,16 @@ const sortedCandidates = computed(() => {
|
|||||||
</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 sortedCandidates" :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">
|
||||||
|
<button @click="toggleSelection(candidate.id)" class="text-amber-400 hover:text-amber-500 hover:scale-110 transition-transform focus:outline-none" :title="candidate.is_selected ? 'Retirer des retenus' : 'Marquer comme retenu'">
|
||||||
|
<svg v-if="candidate.is_selected" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||||
|
</svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-slate-300 hover:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
<div class="font-bold text-slate-900 dark:text-white">{{ candidate.user.name }}</div>
|
<div class="font-bold text-slate-900 dark:text-white">{{ candidate.user.name }}</div>
|
||||||
<div class="text-[10px] text-slate-500 font-bold uppercase tracking-tight">{{ candidate.phone }}</div>
|
<div class="text-[10px] text-slate-500 font-bold uppercase tracking-tight">{{ candidate.phone }}</div>
|
||||||
@@ -294,7 +330,7 @@ const sortedCandidates = computed(() => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="candidates.length === 0">
|
<tr v-if="candidates.length === 0">
|
||||||
<td colspan="7" class="px-6 py-12 text-center text-slate-500 italic">
|
<td colspan="8" class="px-6 py-12 text-center text-slate-500 italic">
|
||||||
Aucun candidat trouvé.
|
Aucun candidat trouvé.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ const updateTenant = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleSelection = () => {
|
||||||
|
router.patch(route('admin.candidates.toggle-selection', props.candidate.id), {}, { preserveScroll: true });
|
||||||
|
};
|
||||||
|
|
||||||
const selectedDocument = ref(null);
|
const selectedDocument = ref(null);
|
||||||
|
|
||||||
const docForm = useForm({
|
const docForm = useForm({
|
||||||
@@ -312,7 +316,27 @@ const runAI = async () => {
|
|||||||
<!-- Profile Card -->
|
<!-- Profile Card -->
|
||||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden">
|
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
<div class="h-24 bg-gradient-to-r from-indigo-500 to-purple-600"></div>
|
<div class="h-24 bg-gradient-to-r from-indigo-500 to-purple-600"></div>
|
||||||
<div class="px-6 pb-6 text-center -mt-12">
|
<div class="px-6 pb-6 text-center -mt-12 relative">
|
||||||
|
<div class="absolute right-6 top-16 right-0 text-center w-full max-w-[50px] ml-auto mr-auto sm:right-6 sm:top-14 sm:w-auto">
|
||||||
|
<button
|
||||||
|
@click="toggleSelection"
|
||||||
|
class="flex flex-col items-center gap-1 group focus:outline-none"
|
||||||
|
:title="candidate.is_selected ? 'Retirer des retenus' : 'Marquer pour entretien'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="p-2 rounded-full transition-all"
|
||||||
|
:class="candidate.is_selected ? 'bg-amber-100 text-amber-500 shadow-sm' : 'bg-slate-100 text-slate-400 group-hover:bg-amber-50 group-hover:text-amber-400'"
|
||||||
|
>
|
||||||
|
<svg v-if="candidate.is_selected" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||||
|
</svg>
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span class="text-[9px] font-black uppercase tracking-widest hidden sm:block" :class="candidate.is_selected ? 'text-amber-500' : 'text-slate-400 group-hover:text-amber-400'">Retenu</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="w-24 h-24 bg-white dark:bg-slate-900 rounded-2xl shadow-xl border-4 border-white dark:border-slate-800 flex items-center justify-center text-4xl font-black text-indigo-600 mx-auto mb-4">
|
<div class="w-24 h-24 bg-white dark:bg-slate-900 rounded-2xl shadow-xl border-4 border-white dark:border-slate-800 flex items-center justify-center text-4xl font-black text-indigo-600 mx-auto mb-4">
|
||||||
{{ candidate.user.name.charAt(0) }}
|
{{ candidate.user.name.charAt(0) }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,11 +46,23 @@ const getStatusColor = (status) => {
|
|||||||
|
|
||||||
<div v-if="isAdmin" class="p-8 space-y-8">
|
<div v-if="isAdmin" class="p-8 space-y-8">
|
||||||
<!-- KPI Cards -->
|
<!-- KPI Cards -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||||
<div class="bg-white dark:bg-slate-800 p-6 rounded-3xl shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-xl transition-all duration-300">
|
<div class="bg-white dark:bg-slate-800 p-6 rounded-3xl shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-xl transition-all duration-300">
|
||||||
<div class="text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-widest">Total Candidats</div>
|
<div class="text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-widest">Total Candidats</div>
|
||||||
<div class="text-4xl font-black mt-2 text-indigo-600 dark:text-indigo-400">{{ stats.total_candidates }}</div>
|
<div class="text-4xl font-black mt-2 text-indigo-600 dark:text-indigo-400">{{ stats.total_candidates }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white dark:bg-slate-800 p-6 rounded-3xl shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-xl transition-all duration-300 relative overflow-hidden group">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-br from-amber-50 to-orange-50 dark:from-amber-900/20 dark:to-orange-900/20 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||||
|
<div class="relative z-10">
|
||||||
|
<div class="text-amber-600 dark:text-amber-500 text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||||
|
</svg>
|
||||||
|
Retenus
|
||||||
|
</div>
|
||||||
|
<div class="text-4xl font-black mt-2 text-amber-600 dark:text-amber-400">{{ stats.selected_candidates }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="bg-white dark:bg-slate-800 p-6 rounded-3xl shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-xl transition-all duration-300">
|
<div class="bg-white dark:bg-slate-800 p-6 rounded-3xl shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-xl transition-all duration-300">
|
||||||
<div class="text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-widest">Tests terminés</div>
|
<div class="text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-widest">Tests terminés</div>
|
||||||
<div class="text-4xl font-black mt-2 text-emerald-600 dark:text-emerald-400">{{ stats.finished_tests }}</div>
|
<div class="text-4xl font-black mt-2 text-emerald-600 dark:text-emerald-400">{{ stats.finished_tests }}</div>
|
||||||
@@ -61,7 +73,7 @@ const getStatusColor = (status) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="bg-white dark:bg-slate-800 p-6 rounded-3xl shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-xl transition-all duration-300">
|
<div class="bg-white dark:bg-slate-800 p-6 rounded-3xl shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-xl transition-all duration-300">
|
||||||
<div class="text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-widest">Meilleur Score</div>
|
<div class="text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-widest">Meilleur Score</div>
|
||||||
<div class="text-4xl font-black mt-2 text-amber-600 dark:text-amber-400">{{ stats.best_score }} / 20</div>
|
<div class="text-4xl font-black mt-2 text-purple-600 dark:text-purple-400">{{ stats.best_score }} / 20</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Route::get('/dashboard', function () {
|
|||||||
$allCandidates = Candidate::with(['attempts'])->get();
|
$allCandidates = Candidate::with(['attempts'])->get();
|
||||||
$stats = [
|
$stats = [
|
||||||
'total_candidates' => Candidate::count(),
|
'total_candidates' => Candidate::count(),
|
||||||
|
'selected_candidates' => Candidate::where('is_selected', true)->count(),
|
||||||
'finished_tests' => Attempt::whereNotNull('finished_at')->count(),
|
'finished_tests' => Attempt::whereNotNull('finished_at')->count(),
|
||||||
'average_score' => round($allCandidates->avg('weighted_score') ?? 0, 1),
|
'average_score' => round($allCandidates->avg('weighted_score') ?? 0, 1),
|
||||||
'best_score' => round($allCandidates->max('weighted_score') ?? 0, 1),
|
'best_score' => round($allCandidates->max('weighted_score') ?? 0, 1),
|
||||||
@@ -86,6 +87,7 @@ Route::middleware('auth')->group(function () {
|
|||||||
Route::patch('/candidates/{candidate}/scores', [\App\Http\Controllers\CandidateController::class, 'updateScores'])->name('candidates.update-scores');
|
Route::patch('/candidates/{candidate}/scores', [\App\Http\Controllers\CandidateController::class, 'updateScores'])->name('candidates.update-scores');
|
||||||
Route::patch('/candidates/{candidate}/position', [\App\Http\Controllers\CandidateController::class, 'updatePosition'])->name('candidates.update-position');
|
Route::patch('/candidates/{candidate}/position', [\App\Http\Controllers\CandidateController::class, 'updatePosition'])->name('candidates.update-position');
|
||||||
Route::patch('/candidates/{candidate}/tenant', [\App\Http\Controllers\CandidateController::class, 'updateTenant'])->name('candidates.update-tenant');
|
Route::patch('/candidates/{candidate}/tenant', [\App\Http\Controllers\CandidateController::class, 'updateTenant'])->name('candidates.update-tenant');
|
||||||
|
Route::patch('/candidates/{candidate}/toggle-selection', [\App\Http\Controllers\CandidateController::class, 'toggleSelection'])->name('candidates.toggle-selection');
|
||||||
Route::post('/candidates/{candidate}/analyze', [\App\Http\Controllers\AIAnalysisController::class, 'analyze'])->name('candidates.analyze');
|
Route::post('/candidates/{candidate}/analyze', [\App\Http\Controllers\AIAnalysisController::class, 'analyze'])->name('candidates.analyze');
|
||||||
Route::post('/candidates/{candidate}/reset-password', [\App\Http\Controllers\CandidateController::class, 'resetPassword'])->name('candidates.reset-password');
|
Route::post('/candidates/{candidate}/reset-password', [\App\Http\Controllers\CandidateController::class, 'resetPassword'])->name('candidates.reset-password');
|
||||||
Route::get('/documents/{document}', [\App\Http\Controllers\DocumentController::class, 'show'])->name('documents.show');
|
Route::get('/documents/{document}', [\App\Http\Controllers\DocumentController::class, 'show'])->name('documents.show');
|
||||||
|
|||||||
Reference in New Issue
Block a user