feat: multi-tenant SaaS implementation - admin interface, tenant isolation, and UI updates

This commit is contained in:
jeremy bayse
2026-03-28 18:38:22 +01:00
parent 7d94be7a8c
commit f53d5770df
20 changed files with 757 additions and 34 deletions

View File

@@ -9,7 +9,8 @@ import DangerButton from '@/Components/DangerButton.vue';
import InputError from '@/Components/InputError.vue';
const props = defineProps({
jobPositions: Array
jobPositions: Array,
tenants: Array
});
const showingModal = ref(false);
@@ -19,7 +20,8 @@ const form = useForm({
title: '',
description: '',
requirements: [],
ai_prompt: ''
ai_prompt: '',
tenant_id: '',
});
const openModal = (position = null) => {
@@ -29,6 +31,7 @@ const openModal = (position = null) => {
form.description = position.description;
form.requirements = position.requirements || [];
form.ai_prompt = position.ai_prompt || '';
form.tenant_id = position.tenant_id || '';
} else {
form.reset();
}
@@ -72,11 +75,14 @@ const removeRequirement = (index) => {
<AdminLayout>
<template #header>
<div class="flex justify-between items-center">
<div class="flex justify-between items-center gap-8">
<h2 class="text-xl font-semibold leading-tight capitalize">
Fiches de Poste (Analyse IA)
</h2>
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>
@@ -90,7 +96,12 @@ const removeRequirement = (index) => {
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="text-[10px] font-black uppercase tracking-widest text-indigo-500 mb-2">Poste / Compétences</div>
<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 }}
@@ -143,6 +154,19 @@ const removeRequirement = (index) => {
<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