Files

268 lines
15 KiB
Vue

<script setup>
import AdminLayout from '@/Layouts/AdminLayout.vue';
import { Head, useForm } from '@inertiajs/vue3';
import { ref } from 'vue';
import Modal from '@/Components/Modal.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import DangerButton from '@/Components/DangerButton.vue';
import InputError from '@/Components/InputError.vue';
const props = defineProps({
jobPositions: Array,
tenants: Array,
quizzes: Array
});
const showingModal = ref(false);
const editingPosition = ref(null);
const form = useForm({
title: '',
description: '',
requirements: [],
ai_prompt: '',
tenant_id: '',
quiz_ids: [],
});
const openModal = (position = null) => {
editingPosition.value = position;
if (position) {
form.title = position.title;
form.description = position.description;
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();
}
showingModal.value = true;
};
const closeModal = () => {
showingModal.value = false;
form.reset();
};
const submit = () => {
if (editingPosition.value) {
form.put(route('admin.job-positions.update', editingPosition.value.id), {
onSuccess: () => closeModal(),
});
} else {
form.post(route('admin.job-positions.store'), {
onSuccess: () => closeModal(),
});
}
};
const deletePosition = (id) => {
if (confirm('Voulez-vous vraiment supprimer cette fiche de poste ?')) {
form.delete(route('admin.job-positions.destroy', id));
}
};
const addRequirement = () => {
form.requirements.push('');
};
const removeRequirement = (index) => {
form.requirements.splice(index, 1);
};
</script>
<template>
<Head title="Fiches de Poste" />
<AdminLayout>
<template #header>
<div class="flex justify-between items-center gap-8">
<h2 class="text-xl font-semibold leading-tight capitalize">
Fiches de Poste
</h2>
<PrimaryButton @click="openModal()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Nouvelle Fiche
</PrimaryButton>
</div>
</template>
<div class="p-8">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div
v-for="position in jobPositions"
:key="position.id"
class="bg-white dark:bg-slate-800 rounded-3xl p-8 shadow-sm border border-slate-200 dark:border-slate-700 hover:shadow-2xl transition-all duration-300 group flex flex-col h-full"
>
<div class="mb-6 flex-1">
<div class="flex justify-between items-start mb-2">
<div class="text-[10px] font-black uppercase tracking-widest text-indigo-500">Poste / Compétences</div>
<div v-if="$page.props.auth.user.role === 'super_admin'" class="text-[9px] font-black uppercase tracking-widest px-2 py-0.5 rounded bg-indigo-50 text-indigo-600 dark:bg-indigo-900/30">
{{ position.tenant ? position.tenant.name : 'Global' }}
</div>
</div>
<h3 class="text-2xl font-black mb-3 group-hover:text-indigo-600 transition-colors">{{ position.title }}</h3>
<p class="text-slate-500 dark:text-slate-400 text-sm line-clamp-3 leading-relaxed">
{{ position.description }}
</p>
</div>
<div class="flex items-center gap-2 mb-6" v-if="position.requirements?.length">
<span
v-for="(req, idx) in position.requirements.slice(0, 3)"
:key="idx"
class="px-2 py-1 bg-slate-100 dark:bg-slate-900 rounded-lg text-[10px] font-bold text-slate-500"
>
{{ req }}
</span>
<span v-if="position.requirements.length > 3" class="text-[10px] text-slate-400 font-bold">
+{{ position.requirements.length - 3 }}
</span>
</div>
<div class="pt-6 border-t border-slate-100 dark:border-slate-700 flex justify-between gap-3">
<SecondaryButton @click="openModal(position)" class="flex-1 !justify-center !py-2 text-xs">Modifier</SecondaryButton>
<button
@click="deletePosition(position.id)"
class="p-2 text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-xl transition-all"
>
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
<!-- Empty State -->
<div v-if="jobPositions.length === 0" class="col-span-full py-32 text-center">
<div class="inline-flex p-6 bg-slate-100 dark:bg-slate-800 rounded-full mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-slate-400" 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>
</div>
<h3 class="text-2xl font-black mb-2">Aucune fiche de poste</h3>
<p class="text-slate-500 mb-8">Créez votre première fiche de poste pour permettre l'analyse IA.</p>
<PrimaryButton @click="openModal()">Créer une fiche</PrimaryButton>
</div>
</div>
</div>
<!-- Modal Create/Edit -->
<Modal :show="showingModal" @close="closeModal">
<div class="p-8">
<h3 class="text-2xl font-black mb-8">{{ editingPosition ? 'Modifier' : 'Créer' }} la Fiche de Poste</h3>
<form @submit.prevent="submit" class="space-y-6">
<div v-if="$page.props.auth.user.role === 'super_admin'" class="mb-4">
<label class="block text-xs font-black uppercase tracking-widest text-slate-400 mb-2">Structure de rattachement</label>
<select
v-model="form.tenant_id"
class="w-full bg-slate-50 dark:bg-slate-900 border-none rounded-2xl p-4 focus:ring-2 focus:ring-indigo-500/20 transition-all font-bold"
required
>
<option value="">Sélectionnez une structure</option>
<option v-for="t in tenants" :key="t.id" :value="t.id">{{ t.name }}</option>
</select>
<InputError :message="form.errors.tenant_id" />
</div>
<div>
<label class="block text-xs font-black uppercase tracking-widest text-slate-400 mb-2">Titre du Poste</label>
<input
v-model="form.title"
type="text"
class="w-full bg-slate-50 dark:bg-slate-900 border-none rounded-2xl p-4 focus:ring-2 focus:ring-indigo-500/20 transition-all font-bold"
placeholder="Ex: Développeur Fullstack Senior"
required
>
<InputError :message="form.errors.title" />
</div>
<div>
<label class="block text-xs font-black uppercase tracking-widest text-slate-400 mb-2">Description / Fiche de Poste</label>
<textarea
v-model="form.description"
rows="8"
class="w-full bg-slate-50 dark:bg-slate-900 border-none rounded-2xl p-4 focus:ring-2 focus:ring-indigo-500/20 transition-all text-sm leading-relaxed"
placeholder="Détaillez les missions et les attentes pour ce poste..."
required
></textarea>
<InputError :message="form.errors.description" />
</div>
<div class="bg-indigo-50/50 dark:bg-indigo-900/10 p-6 rounded-3xl border border-indigo-100 dark:border-indigo-800/50">
<label class="block text-xs font-black uppercase tracking-widest text-indigo-600 dark:text-indigo-400 mb-2">IA Context & Prompt Personnalisé</label>
<p class="text-[10px] text-indigo-400 mb-4 font-bold uppercase tracking-tight">Utilisez cette zone pour donner des instructions spécifiques à l'IA (priorités, contexte entreprise, ton de l'analyse...)</p>
<textarea
v-model="form.ai_prompt"
rows="5"
class="w-full bg-white dark:bg-slate-900 border-none rounded-2xl p-4 focus:ring-2 focus:ring-indigo-500/20 transition-all text-sm leading-relaxed"
placeholder="Ex: Sois particulièrement attentif à l'expérience sur des projets SaaS à forte charge. Favorise les candidats ayant travaillé en environnement Agile."
></textarea>
<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>
<button type="button" @click="addRequirement" class="text-[10px] font-black text-indigo-500 uppercase hover:underline">+ Ajouter</button>
</div>
<div class="space-y-3">
<div v-for="(req, index) in form.requirements" :key="index" class="flex gap-2">
<input
v-model="form.requirements[index]"
type="text"
class="flex-1 bg-slate-50 dark:bg-slate-900 border-none rounded-xl p-3 text-xs font-bold focus:ring-2 focus:ring-indigo-500/20 transition-all"
placeholder="Ex: Maitrise de Laravel / Vue.js"
>
<button type="button" @click="removeRequirement(index)" class="p-2 text-slate-400 hover:text-red-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<div class="pt-8 border-t border-slate-100 dark:border-slate-800 flex justify-end gap-3">
<SecondaryButton @click="closeModal" :disabled="form.processing">Annuler</SecondaryButton>
<PrimaryButton :disabled="form.processing">
{{ editingPosition ? 'Mettre à jour' : 'Enregistrer' }}
</PrimaryButton>
</div>
</form>
</div>
</Modal>
</AdminLayout>
</template>