feat: link quizzes to job positions and filter candidate dashboard accordingly
This commit is contained in:
@@ -13,8 +13,9 @@ class JobPositionController extends Controller
|
||||
$this->authorizeAdmin();
|
||||
|
||||
return Inertia::render('Admin/JobPositions/Index', [
|
||||
'jobPositions' => JobPosition::with('tenant')->get(),
|
||||
'tenants' => \App\Models\Tenant::orderBy('name')->get()
|
||||
'jobPositions' => JobPosition::with(['tenant', 'quizzes'])->get(),
|
||||
'tenants' => \App\Models\Tenant::orderBy('name')->get(),
|
||||
'quizzes' => \App\Models\Quiz::all()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -28,9 +29,11 @@ class JobPositionController extends Controller
|
||||
'requirements' => 'nullable|array',
|
||||
'ai_prompt' => 'nullable|string',
|
||||
'tenant_id' => 'nullable|exists:tenants,id',
|
||||
'quiz_ids' => 'nullable|array',
|
||||
'quiz_ids.*' => 'exists:quizzes,id',
|
||||
]);
|
||||
|
||||
JobPosition::create([
|
||||
$jobPosition = JobPosition::create([
|
||||
'title' => $request->title,
|
||||
'description' => $request->description,
|
||||
'requirements' => $request->requirements,
|
||||
@@ -38,6 +41,10 @@ class JobPositionController extends Controller
|
||||
'tenant_id' => auth()->user()->isSuperAdmin() ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
]);
|
||||
|
||||
if ($request->has('quiz_ids')) {
|
||||
$jobPosition->quizzes()->sync($request->quiz_ids);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Fiche de poste créée avec succès.');
|
||||
}
|
||||
|
||||
@@ -51,6 +58,8 @@ class JobPositionController extends Controller
|
||||
'requirements' => 'nullable|array',
|
||||
'ai_prompt' => 'nullable|string',
|
||||
'tenant_id' => 'nullable|exists:tenants,id',
|
||||
'quiz_ids' => 'nullable|array',
|
||||
'quiz_ids.*' => 'exists:quizzes,id',
|
||||
]);
|
||||
|
||||
$jobPosition->update([
|
||||
@@ -61,6 +70,10 @@ class JobPositionController extends Controller
|
||||
'tenant_id' => auth()->user()->isSuperAdmin() ? $request->tenant_id : auth()->user()->tenant_id,
|
||||
]);
|
||||
|
||||
if ($request->has('quiz_ids')) {
|
||||
$jobPosition->quizzes()->sync($request->quiz_ids);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Fiche de poste mise à jour.');
|
||||
}
|
||||
|
||||
|
||||
@@ -22,4 +22,9 @@ class JobPosition extends Model
|
||||
{
|
||||
return $this->hasMany(Candidate::class);
|
||||
}
|
||||
|
||||
public function quizzes()
|
||||
{
|
||||
return $this->belongsToMany(Quiz::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,9 @@ class Quiz extends Model
|
||||
{
|
||||
return $this->hasMany(Question::class);
|
||||
}
|
||||
|
||||
public function jobPositions()
|
||||
{
|
||||
return $this->belongsToMany(JobPosition::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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::create('job_position_quiz', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('job_position_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('quiz_id')->constrained()->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('job_position_quiz');
|
||||
}
|
||||
};
|
||||
@@ -10,7 +10,8 @@ import InputError from '@/Components/InputError.vue';
|
||||
|
||||
const props = defineProps({
|
||||
jobPositions: Array,
|
||||
tenants: Array
|
||||
tenants: Array,
|
||||
quizzes: Array
|
||||
});
|
||||
|
||||
const showingModal = ref(false);
|
||||
@@ -22,6 +23,7 @@ const form = useForm({
|
||||
requirements: [],
|
||||
ai_prompt: '',
|
||||
tenant_id: '',
|
||||
quiz_ids: [],
|
||||
});
|
||||
|
||||
const openModal = (position = null) => {
|
||||
@@ -32,6 +34,7 @@ const openModal = (position = null) => {
|
||||
form.requirements = position.requirements || [];
|
||||
form.ai_prompt = position.ai_prompt || '';
|
||||
form.tenant_id = position.tenant_id || '';
|
||||
form.quiz_ids = position.quizzes ? position.quizzes.map(q => q.id) : [];
|
||||
} else {
|
||||
form.reset();
|
||||
}
|
||||
@@ -203,6 +206,32 @@ const removeRequirement = (index) => {
|
||||
<InputError :message="form.errors.ai_prompt" />
|
||||
</div>
|
||||
|
||||
<div v-if="quizzes && quizzes.length > 0">
|
||||
<label class="block text-xs font-black uppercase tracking-widest text-slate-400 mb-4">Tests techniques associés</label>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div
|
||||
v-for="quiz in quizzes"
|
||||
:key="quiz.id"
|
||||
class="flex items-center p-3 bg-slate-50 dark:bg-slate-900 rounded-2xl cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
||||
@click="form.quiz_ids.includes(quiz.id) ? form.quiz_ids = form.quiz_ids.filter(id => id !== quiz.id) : form.quiz_ids.push(quiz.id)"
|
||||
>
|
||||
<div
|
||||
class="w-5 h-5 rounded-md border-2 mr-3 flex items-center justify-center transition-all"
|
||||
:class="form.quiz_ids.includes(quiz.id) ? 'bg-indigo-600 border-indigo-600' : 'border-slate-300 dark:border-slate-600'"
|
||||
>
|
||||
<svg v-if="form.quiz_ids.includes(quiz.id)" xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold leading-tight">{{ quiz.title }}</div>
|
||||
<div class="text-[9px] text-slate-400 uppercase tracking-tighter">{{ quiz.duration_minutes }} min</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<InputError :message="form.errors.quiz_ids" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<label class="text-xs font-black uppercase tracking-widest text-slate-400">Compétences clés / Pré-requis</label>
|
||||
|
||||
@@ -48,8 +48,13 @@ Route::get('/dashboard', function () {
|
||||
->values()
|
||||
->all();
|
||||
} else {
|
||||
$candidate = auth()->user()->candidate;
|
||||
$quizzes = \App\Models\Quiz::all()->map(function($quiz) use ($candidate) {
|
||||
$candidate = auth()->user()->candidate?->load('jobPosition.quizzes');
|
||||
|
||||
$quizzes = ($candidate && $candidate->jobPosition)
|
||||
? $candidate->jobPosition->quizzes
|
||||
: collect();
|
||||
|
||||
$quizzes = $quizzes->map(function($quiz) use ($candidate) {
|
||||
$quiz->has_finished_attempt = $candidate
|
||||
? $candidate->attempts()->where('quiz_id', $quiz->id)->whereNotNull('finished_at')->exists()
|
||||
: false;
|
||||
|
||||
Reference in New Issue
Block a user