Refactoring AI candidate analysis: UI improvements, data normalization, provider management and real-time score clamping
This commit is contained in:
35
resources/js/Components/Rq/RqBadge.vue
Normal file
35
resources/js/Components/Rq/RqBadge.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
/**
|
||||
* RqBadge.vue — Badge de statut candidat
|
||||
*
|
||||
* @prop status 'en_attente' | 'en_cours' | 'termine' | 'refuse'
|
||||
* @prop label Override du texte affiché (optionnel)
|
||||
*/
|
||||
const props = defineProps({
|
||||
status: { type: String, required: true },
|
||||
label: { type: String, default: null },
|
||||
});
|
||||
|
||||
const labels = {
|
||||
en_attente: 'En attente',
|
||||
en_cours: 'En cours',
|
||||
termine: 'Terminé',
|
||||
refuse: 'Refusé',
|
||||
};
|
||||
|
||||
const displayLabel = props.label ?? labels[props.status] ?? props.status;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block px-2.5 py-0.5 rounded-full text-2xs font-black uppercase tracking-[0.12em]',
|
||||
status === 'en_attente' && 'bg-ink/5 text-ink/55',
|
||||
status === 'en_cours' && 'bg-primary/10 text-primary',
|
||||
status === 'termine' && 'bg-success/10 text-success',
|
||||
status === 'refuse' && 'bg-accent/10 text-accent',
|
||||
]"
|
||||
>
|
||||
{{ displayLabel }}
|
||||
</span>
|
||||
</template>
|
||||
45
resources/js/Components/Rq/RqButton.vue
Normal file
45
resources/js/Components/Rq/RqButton.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
/**
|
||||
* RqButton.vue — Bouton principal RecruitQuizz
|
||||
*
|
||||
* @prop variant 'primary' | 'ghost' | 'danger' | 'outline'
|
||||
* @prop size 'sm' | 'md' | 'lg'
|
||||
* @prop disabled Boolean
|
||||
* @prop loading Boolean — affiche un spinner
|
||||
* @prop icon SVG path string optionnel (gauche du label)
|
||||
*/
|
||||
defineProps({
|
||||
variant: { type: String, default: 'primary' },
|
||||
size: { type: String, default: 'md' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
loading: { type: Boolean, default: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
v-bind="$attrs"
|
||||
:disabled="disabled || loading"
|
||||
:class="[
|
||||
// Base
|
||||
'inline-flex items-center justify-center gap-1.5 font-sans font-extrabold uppercase tracking-[0.08em] transition-all duration-150 select-none focus:outline-none focus:ring-2 focus:ring-offset-2 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
// Size
|
||||
size === 'sm' && 'text-2xs px-3.5 py-2 rounded-lg',
|
||||
size === 'md' && 'text-xs px-5 py-2.5 rounded-[10px]',
|
||||
size === 'lg' && 'text-sm px-7 py-3.5 rounded-[10px]',
|
||||
// Variant
|
||||
variant === 'primary' && 'bg-highlight text-highlight-dark shadow-gold hover:brightness-105 hover:-translate-y-px hover:shadow-lg focus:ring-highlight/50',
|
||||
variant === 'ghost' && 'bg-transparent text-ink border border-ink/10 hover:bg-primary/5 hover:border-primary hover:text-primary focus:ring-primary/30',
|
||||
variant === 'outline' && 'bg-transparent text-primary border-2 border-primary hover:bg-primary hover:text-white focus:ring-primary/30',
|
||||
variant === 'danger' && 'bg-accent/10 text-accent border border-accent/20 hover:bg-accent hover:text-white focus:ring-accent/30',
|
||||
]"
|
||||
>
|
||||
<!-- Spinner -->
|
||||
<svg v-if="loading" class="animate-spin h-4 w-4 opacity-70" viewBox="0 0 24 24" fill="none">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
|
||||
</svg>
|
||||
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
33
resources/js/Components/Rq/RqCard.vue
Normal file
33
resources/js/Components/Rq/RqCard.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
/**
|
||||
* RqCard.vue — Carte conteneur RecruitQuizz
|
||||
*
|
||||
* @prop padding 'none' | 'sm' | 'md' | 'lg'
|
||||
* @prop hover Boolean — active l'effet de survol (lift)
|
||||
* @prop accent 'primary' | 'gold' | 'green' | 'red' — bordure colorée en bas
|
||||
*/
|
||||
defineProps({
|
||||
padding: { type: String, default: 'md' },
|
||||
hover: { type: Boolean, default: false },
|
||||
accent: { type: String, default: null },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'bg-surface rounded-2xl border border-ink/[0.07] shadow-sm',
|
||||
hover && 'transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md',
|
||||
accent === 'primary' && 'border-b-2 border-b-primary',
|
||||
accent === 'gold' && 'border-b-2 border-b-highlight',
|
||||
accent === 'green' && 'border-b-2 border-b-success',
|
||||
accent === 'red' && 'border-b-2 border-b-accent',
|
||||
padding === 'none' && 'overflow-hidden',
|
||||
padding === 'sm' && 'p-4',
|
||||
padding === 'md' && 'p-5',
|
||||
padding === 'lg' && 'p-6',
|
||||
]"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
65
resources/js/Components/Rq/RqInput.vue
Normal file
65
resources/js/Components/Rq/RqInput.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup>
|
||||
/**
|
||||
* RqInput.vue — Champ de saisie RecruitQuizz
|
||||
*
|
||||
* @prop modelValue String (v-model)
|
||||
* @prop placeholder String
|
||||
* @prop type String (text, email, password, search…)
|
||||
* @prop icon 'search' | 'mail' | 'lock' — icône préfixe
|
||||
* @prop error String — message d'erreur
|
||||
* @prop label String — label au-dessus du champ
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
type: { type: String, default: 'text' },
|
||||
icon: { type: String, default: null },
|
||||
error: { type: String, default: null },
|
||||
label: { type: String, default: null },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const iconPaths = {
|
||||
search: 'M11 17.25a6.25 6.25 0 110-12.5 6.25 6.25 0 010 12.5zM16 16l4.5 4.5',
|
||||
mail: 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6',
|
||||
lock: 'M19 11H5a2 2 0 00-2 2v7a2 2 0 002 2h14a2 2 0 002-2v-7a2 2 0 00-2-2zM7 11V7a5 5 0 0110 0v4',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-1.5 w-full">
|
||||
<!-- Label -->
|
||||
<label v-if="label" class="text-2xs font-black uppercase tracking-[0.14em] text-ink/50">
|
||||
{{ label }}
|
||||
</label>
|
||||
|
||||
<!-- Input wrapper -->
|
||||
<div class="relative">
|
||||
<!-- Icon -->
|
||||
<span v-if="icon" class="absolute left-3 top-1/2 -translate-y-1/2 text-ink/30 pointer-events-none">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path :d="iconPaths[icon]" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<input
|
||||
:type="type"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
@input="emit('update:modelValue', $event.target.value)"
|
||||
:class="[
|
||||
'w-full rounded-[10px] border bg-surface font-sans text-sm font-semibold text-ink placeholder:text-ink/30 outline-none transition-all duration-150',
|
||||
'focus:border-primary focus:ring-2 focus:ring-primary/15',
|
||||
icon ? 'pl-9 pr-4 py-2.5' : 'px-4 py-2.5',
|
||||
error ? 'border-accent focus:border-accent focus:ring-accent/15' : 'border-ink/10',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<p v-if="error" class="text-2xs font-bold text-accent">{{ error }}</p>
|
||||
</div>
|
||||
</template>
|
||||
38
resources/js/Components/Rq/RqScoreBar.vue
Normal file
38
resources/js/Components/Rq/RqScoreBar.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
/**
|
||||
* RqScoreBar.vue — Barre de score pondéré
|
||||
*
|
||||
* @prop value Number — score actuel
|
||||
* @prop max Number — score maximum (défaut 20)
|
||||
* @prop showLabel Boolean — affiche "X/max" à droite
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: Number, required: true },
|
||||
max: { type: Number, default: 20 },
|
||||
showLabel: { type: Boolean, default: true },
|
||||
});
|
||||
|
||||
const pct = computed(() => Math.min(100, (props.value / props.max) * 100));
|
||||
|
||||
const colorClass = computed(() => {
|
||||
if (pct.value >= 80) return 'bg-success';
|
||||
if (pct.value >= 60) return 'bg-highlight';
|
||||
return 'bg-accent';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2.5 w-full">
|
||||
<div class="flex-1 h-[5px] bg-ink/[0.07] rounded-full overflow-hidden">
|
||||
<div
|
||||
:class="['h-full rounded-full transition-all duration-500', colorClass]"
|
||||
:style="{ width: `${pct}%` }"
|
||||
/>
|
||||
</div>
|
||||
<span v-if="showLabel" class="text-xs font-black text-ink tabular-nums min-w-[44px] text-right">
|
||||
{{ value }}<span class="text-[9px] text-ink/40 font-semibold">/{{ max }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
54
resources/js/Components/Rq/RqStatCard.vue
Normal file
54
resources/js/Components/Rq/RqStatCard.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
/**
|
||||
* RqStatCard.vue — Carte KPI dashboard admin
|
||||
*
|
||||
* @prop label String — libellé (ex: "Total Candidats")
|
||||
* @prop value String | Number — valeur principale
|
||||
* @prop sub String — sous-texte (ex: "+3 ce mois")
|
||||
* @prop color 'primary' | 'gold' | 'green' | 'sky' | 'red'
|
||||
* @prop unit String — unité affichée après la valeur (ex: "/ 20")
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
label: { type: String, required: true },
|
||||
value: { type: [String, Number], required: true },
|
||||
sub: { type: String, default: null },
|
||||
color: { type: String, default: 'primary' },
|
||||
unit: { type: String, default: null },
|
||||
});
|
||||
|
||||
const colorMap = {
|
||||
primary: { text: 'text-primary', glow: 'from-primary/10' },
|
||||
gold: { text: 'text-highlight', glow: 'from-highlight/15' },
|
||||
green: { text: 'text-success', glow: 'from-success/10' },
|
||||
sky: { text: 'text-primary-light', glow: 'from-primary-light/10' },
|
||||
red: { text: 'text-accent', glow: 'from-accent/10' },
|
||||
};
|
||||
|
||||
const c = computed(() => colorMap[props.color] ?? colorMap.primary);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative bg-surface rounded-2xl border border-ink/[0.07] shadow-sm p-5 overflow-hidden
|
||||
transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md group cursor-default">
|
||||
|
||||
<!-- Label -->
|
||||
<p class="text-2xs font-black uppercase tracking-[0.18em] text-ink/40 mb-2.5">
|
||||
{{ label }}
|
||||
</p>
|
||||
|
||||
<!-- Value -->
|
||||
<p :class="['font-serif font-black leading-none', c.text]"
|
||||
style="font-size: clamp(1.75rem, 2.5vw, 2.25rem);">
|
||||
{{ value }}
|
||||
<span v-if="unit" class="text-base font-sans font-bold text-ink/30 ml-1">{{ unit }}</span>
|
||||
</p>
|
||||
|
||||
<!-- Sub -->
|
||||
<p v-if="sub" class="mt-1.5 text-2xs font-semibold text-ink/40">{{ sub }}</p>
|
||||
|
||||
<!-- Decorative blob -->
|
||||
<div :class="['absolute bottom-0 right-0 w-20 h-20 rounded-full bg-gradient-radial to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500', c.glow]" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,203 +1,264 @@
|
||||
<script setup>
|
||||
/**
|
||||
* AdminLayout.vue — Layout principal admin redesigné
|
||||
* RecruitQuizz v2.4
|
||||
*
|
||||
* Changements vs version précédente :
|
||||
* - Sidebar 220px (collapsible → 64px) avec transition douce
|
||||
* - Header 60px épuré, titre avec accent bar or
|
||||
* - Fond neutral (#f0ece4) sur le contenu principal
|
||||
* - Items nav : rounded-xl, active bg-highlight text-highlight-dark
|
||||
* - Footer sidebar : avatar + nom + version + logout
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import ApplicationLogo from '@/Components/ApplicationLogo.vue';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import Dropdown from '@/Components/Dropdown.vue';
|
||||
import DropdownLink from '@/Components/DropdownLink.vue';
|
||||
import EnvironmentBanner from '@/Components/EnvironmentBanner.vue';
|
||||
|
||||
const page = usePage();
|
||||
const isSidebarOpen = ref(true);
|
||||
|
||||
// ─── Navigation ────────────────────────────────────────────────────────────
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
route: 'dashboard',
|
||||
label: 'Tableau de bord',
|
||||
icon: 'M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2V9z M9 22V12h6v10',
|
||||
},
|
||||
{
|
||||
route: 'admin.candidates.index',
|
||||
match: 'admin.candidates.*',
|
||||
label: 'Candidats',
|
||||
icon: 'M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2 M9 11a4 4 0 100-8 4 4 0 000 8z M23 21v-2a4 4 0 00-3-3.87 M16 3.13a4 4 0 010 7.75',
|
||||
},
|
||||
{
|
||||
route: 'admin.quizzes.index',
|
||||
match: 'admin.quizzes.*',
|
||||
label: 'Quiz',
|
||||
icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M9 12l2 2 4-4',
|
||||
},
|
||||
{
|
||||
route: 'admin.job-positions.index',
|
||||
match: 'admin.job-positions.*',
|
||||
label: 'Fiches de Poste',
|
||||
icon: 'M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z',
|
||||
},
|
||||
{
|
||||
route: 'admin.comparative',
|
||||
label: 'Comparateur',
|
||||
icon: 'M18 20V10 M12 20V4 M6 20v-6',
|
||||
},
|
||||
];
|
||||
|
||||
const superAdminItems = [
|
||||
{
|
||||
route: 'admin.tenants.index',
|
||||
match: 'admin.tenants.*',
|
||||
label: 'Structures',
|
||||
icon: 'M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4',
|
||||
},
|
||||
{
|
||||
route: 'admin.users.index',
|
||||
match: 'admin.users.*',
|
||||
label: 'Équipe SaaS',
|
||||
icon: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z',
|
||||
},
|
||||
{
|
||||
route: 'admin.logs.index',
|
||||
match: 'admin.logs.*',
|
||||
label: 'Logs connexion',
|
||||
icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01',
|
||||
},
|
||||
];
|
||||
|
||||
const isActive = (item) => {
|
||||
if (item.match) return route().current(item.match);
|
||||
return route().current(item.route);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EnvironmentBanner />
|
||||
<div class="min-h-screen bg-sand flex text-anthracite font-sans selection:bg-highlight selection:text-anthracite">
|
||||
<!-- Sidebar -->
|
||||
|
||||
<div class="min-h-screen flex bg-neutral font-sans text-ink selection:bg-highlight selection:text-highlight-dark">
|
||||
|
||||
<!-- ─── Sidebar ──────────────────────────────────────────────────── -->
|
||||
<aside
|
||||
:class="[isSidebarOpen ? 'w-64' : 'w-20']"
|
||||
class="hidden md:flex flex-col bg-primary transition-all duration-300 shadow-xl z-20"
|
||||
:class="[
|
||||
isSidebarOpen ? 'w-[220px]' : 'w-16',
|
||||
'hidden md:flex flex-col bg-primary shadow-xl z-20 transition-all duration-300 shrink-0'
|
||||
]"
|
||||
>
|
||||
<div class="h-16 flex items-center px-5 bg-primary/90 border-b border-white/10">
|
||||
<Link :href="route('dashboard')" class="flex items-center gap-3 overflow-hidden ml-1">
|
||||
<div class="w-8 h-8 bg-highlight rounded flex items-center justify-center shrink-0 shadow-sm shadow-highlight/20">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#3a2800]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9l-.707.707M12 18v3m4.95-4.95l.707.707M12 3c-4.418 0-8 3.582-8 8 0 2.209.895 4.209 2.343 5.657L12 21l5.657-5.343A7.994 7.994 0 0020 11c0-4.418-3.582-8-8-8z" />
|
||||
<!-- Logo -->
|
||||
<div class="h-[60px] flex items-center border-b border-white/[0.07] px-4 shrink-0">
|
||||
<Link :href="route('dashboard')" class="flex items-center gap-3 overflow-hidden">
|
||||
<!-- Icône -->
|
||||
<div class="w-[30px] h-[30px] bg-highlight rounded-lg flex items-center justify-center shrink-0 shadow-gold">
|
||||
<svg class="w-4 h-4 text-highlight-dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span v-if="isSidebarOpen" class="font-serif font-black text-xl tracking-tight whitespace-nowrap text-white">RECRU<span class="text-accent italic px-0.5">IT</span></span>
|
||||
<!-- Wordmark -->
|
||||
<Transition name="fade">
|
||||
<span v-if="isSidebarOpen" class="font-serif font-black text-[17px] text-white tracking-tight whitespace-nowrap">
|
||||
RECRU<span class="text-highlight italic">IT</span>
|
||||
</span>
|
||||
</Transition>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 py-6 px-3 space-y-1.5 overflow-y-auto custom-scrollbar">
|
||||
<Link
|
||||
:href="route('dashboard')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('dashboard') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Tableau de bord</span>
|
||||
</Link>
|
||||
<!-- Nav principale -->
|
||||
<nav class="flex-1 px-2.5 py-4 space-y-0.5 overflow-y-auto scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent">
|
||||
<!-- Items principaux -->
|
||||
<template v-for="item in navItems" :key="item.route">
|
||||
<Link
|
||||
:href="route(item.route)"
|
||||
:title="!isSidebarOpen ? item.label : undefined"
|
||||
:class="[
|
||||
'flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-150 font-sans font-bold text-[12.5px] tracking-[0.01em]',
|
||||
isSidebarOpen ? '' : 'justify-center',
|
||||
isActive(item)
|
||||
? 'bg-highlight text-highlight-dark shadow-md shadow-highlight/20'
|
||||
: 'text-white/70 hover:bg-white/10 hover:text-white',
|
||||
]"
|
||||
>
|
||||
<svg class="w-[17px] h-[17px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path :d="item.icon"/>
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">{{ item.label }}</span>
|
||||
</Link>
|
||||
</template>
|
||||
|
||||
<Link
|
||||
:href="route('admin.candidates.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.candidates.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Candidats</span>
|
||||
</Link>
|
||||
<!-- Section super admin -->
|
||||
<template v-if="$page.props.auth.user.role === 'super_admin'">
|
||||
<div class="pt-4 pb-2">
|
||||
<div
|
||||
v-if="isSidebarOpen"
|
||||
class="px-3 text-[9px] font-black uppercase tracking-[0.18em] text-white/25"
|
||||
>Configuration</div>
|
||||
<div v-else class="h-px w-8 mx-auto bg-white/10" />
|
||||
</div>
|
||||
|
||||
<Link
|
||||
:href="route('admin.quizzes.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.quizzes.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Quiz</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
:href="route('admin.job-positions.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.job-positions.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" 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>
|
||||
<span v-if="isSidebarOpen" class="truncate">Fiches de Poste</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
:href="route('admin.comparative')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.comparative') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Comparateur</span>
|
||||
</Link>
|
||||
|
||||
<div v-if="$page.props.auth.user.role === 'super_admin'" class="pt-4 pb-2">
|
||||
<div v-show="isSidebarOpen" class="px-3 text-[10px] font-black uppercase tracking-widest text-white/30">Configuration</div>
|
||||
<div v-show="!isSidebarOpen" class="h-[1px] w-8 mx-auto bg-white/10"></div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
:href="route('admin.tenants.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.tenants.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Structures</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
:href="route('admin.users.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.users.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Équipe SaaS</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
:href="route('admin.logs.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.logs.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Logs de connexion</span>
|
||||
</Link>
|
||||
<template v-for="item in superAdminItems" :key="item.route">
|
||||
<Link
|
||||
:href="route(item.route)"
|
||||
:title="!isSidebarOpen ? item.label : undefined"
|
||||
:class="[
|
||||
'flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-150 font-sans font-bold text-[12.5px] tracking-[0.01em]',
|
||||
isSidebarOpen ? '' : 'justify-center',
|
||||
isActive(item)
|
||||
? 'bg-highlight text-highlight-dark shadow-md shadow-highlight/20'
|
||||
: 'text-white/70 hover:bg-white/10 hover:text-white',
|
||||
]"
|
||||
>
|
||||
<svg class="w-[17px] h-[17px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path :d="item.icon"/>
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">{{ item.label }}</span>
|
||||
</Link>
|
||||
</template>
|
||||
</template>
|
||||
</nav>
|
||||
|
||||
<div class="p-4 border-t border-white/10 bg-primary/80">
|
||||
<button
|
||||
@click="isSidebarOpen = !isSidebarOpen"
|
||||
class="flex items-center justify-center w-full h-10 rounded-lg text-white/50 hover:bg-white/10 hover:text-white transition-all duration-300"
|
||||
>
|
||||
<svg v-if="isSidebarOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<div v-show="isSidebarOpen" class="mt-4 text-[9px] font-bold uppercase tracking-widest text-[#3a7abf] text-center">
|
||||
App v{{ $page.props.app_version }}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col min-w-0 overflow-hidden bg-neutral">
|
||||
<header class="h-16 shrink-0 flex items-center justify-between px-8 bg-white border-b border-anthracite/5 shadow-sm z-10 relative">
|
||||
<div>
|
||||
<h2 v-if="$slots.header" class="font-serif font-black text-xl text-primary capitalize tracking-tight flex items-center gap-3">
|
||||
<div class="w-1.5 h-6 bg-highlight rounded-full hidden md:block"></div>
|
||||
<slot name="header" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Footer sidebar : user + collapse -->
|
||||
<div class="px-3 py-3 border-t border-white/[0.07] shrink-0">
|
||||
<!-- User info (sidebar ouverte) -->
|
||||
<div v-if="isSidebarOpen" class="flex items-center gap-2.5 mb-3">
|
||||
<div class="w-8 h-8 rounded-full bg-highlight flex items-center justify-center text-[12px] font-black text-highlight-dark shrink-0">
|
||||
{{ $page.props.auth.user.name.charAt(0) }}
|
||||
</div>
|
||||
<div class="overflow-hidden flex-1 min-w-0">
|
||||
<div class="text-[12px] font-bold text-white truncate">{{ $page.props.auth.user.name }}</div>
|
||||
<div class="text-[10px] text-white/40 truncate">{{ $page.props.auth.user.role }}</div>
|
||||
</div>
|
||||
<Dropdown align="right" width="48">
|
||||
<template #trigger>
|
||||
<button class="flex items-center gap-2 text-sm font-subtitle font-bold hover:text-primary transition-colors cursor-pointer py-2">
|
||||
<div class="w-8 h-8 rounded-full bg-sand flex items-center justify-center text-primary border border-primary/10">
|
||||
{{ $page.props.auth.user.name.charAt(0) }}
|
||||
</div>
|
||||
<span class="hidden md:block">{{ $page.props.auth.user.name }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-anthracite/40" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
<button class="p-1.5 rounded-lg text-white/40 hover:text-white hover:bg-white/10 transition-colors">
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<DropdownLink :href="route('profile.edit')">Paramètres du profil</DropdownLink>
|
||||
<DropdownLink :href="route('admin.backup')" as="a" class="!text-sky font-bold" v-if="$page.props.auth.user.role === 'super_admin'">Sauvegarde Base de données</DropdownLink>
|
||||
<div class="border-t border-anthracite/5 my-1"></div>
|
||||
<DropdownLink :href="route('logout')" method="post" as="button" class="!text-accent font-bold">Se déconnecter</DropdownLink>
|
||||
<DropdownLink
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
:href="route('admin.backup')"
|
||||
as="a"
|
||||
class="!text-primary-light font-bold"
|
||||
>Sauvegarde BDD</DropdownLink>
|
||||
<div class="border-t border-ink/5 my-1"/>
|
||||
<DropdownLink :href="route('logout')" method="post" as="button" class="!text-accent font-bold">
|
||||
Se déconnecter
|
||||
</DropdownLink>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<!-- Collapse button -->
|
||||
<button
|
||||
@click="isSidebarOpen = !isSidebarOpen"
|
||||
class="flex items-center justify-center w-full h-9 rounded-xl text-white/40 hover:bg-white/10 hover:text-white transition-all duration-200"
|
||||
:title="isSidebarOpen ? 'Réduire' : 'Agrandir'"
|
||||
>
|
||||
<svg class="w-4 h-4 transition-transform duration-300" :class="isSidebarOpen ? '' : 'rotate-180'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M15 18l-6-6 6-6"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Version -->
|
||||
<div v-if="isSidebarOpen" class="mt-2 text-center text-[9px] font-bold uppercase tracking-[0.12em] text-white/20">
|
||||
v{{ $page.props.app_version }}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ─── Main ─────────────────────────────────────────────────────── -->
|
||||
<div class="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="h-[60px] shrink-0 flex items-center justify-between px-8 bg-surface border-b border-ink/[0.05] shadow-xs z-10">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Accent bar -->
|
||||
<div class="w-[3px] h-5 bg-highlight rounded-full hidden md:block" />
|
||||
<!-- Page title -->
|
||||
<h2 v-if="$slots.header" class="font-serif font-black text-[17px] text-primary tracking-tight">
|
||||
<slot name="header" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Slot pour actions contextuelles (ex: bouton "Nouveau") -->
|
||||
<slot name="actions" />
|
||||
|
||||
<!-- Badge rôle -->
|
||||
<span
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
class="bg-gradient-to-r from-accent to-highlight text-white px-3 py-1 rounded-full text-[9px] font-black tracking-widest uppercase shadow-sm"
|
||||
>GOD MODE</span>
|
||||
<span
|
||||
v-else-if="$page.props.auth.user.tenant"
|
||||
class="bg-primary/10 text-primary px-3 py-1 rounded-full text-[9px] font-black tracking-widest uppercase border border-primary/20"
|
||||
>{{ $page.props.auth.user.tenant.name }}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 overflow-y-auto p-4 md:p-8">
|
||||
<!-- Content -->
|
||||
<main class="flex-1 overflow-y-auto p-7">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Firefox scrollbar config for sidebar */
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
<style scoped>
|
||||
.fade-enter-active, .fade-leave-active { transition: opacity 0.15s ease; }
|
||||
.fade-enter-from, .fade-leave-to { opacity: 0; }
|
||||
|
||||
/* Scrollbar sidebar Firefox */
|
||||
.scrollbar-thin { scrollbar-width: thin; }
|
||||
.scrollbar-thumb-white\/10 { scrollbar-color: rgba(255,255,255,0.1) transparent; }
|
||||
</style>
|
||||
|
||||
203
resources/js/Layouts/AdminLayout.vue.backup
Normal file
203
resources/js/Layouts/AdminLayout.vue.backup
Normal file
@@ -0,0 +1,203 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import ApplicationLogo from '@/Components/ApplicationLogo.vue';
|
||||
import Dropdown from '@/Components/Dropdown.vue';
|
||||
import DropdownLink from '@/Components/DropdownLink.vue';
|
||||
import EnvironmentBanner from '@/Components/EnvironmentBanner.vue';
|
||||
|
||||
const isSidebarOpen = ref(true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EnvironmentBanner />
|
||||
<div class="min-h-screen bg-sand flex text-anthracite font-sans selection:bg-highlight selection:text-anthracite">
|
||||
<!-- Sidebar -->
|
||||
<aside
|
||||
:class="[isSidebarOpen ? 'w-64' : 'w-20']"
|
||||
class="hidden md:flex flex-col bg-primary transition-all duration-300 shadow-xl z-20"
|
||||
>
|
||||
<div class="h-16 flex items-center px-5 bg-primary/90 border-b border-white/10">
|
||||
<Link :href="route('dashboard')" class="flex items-center gap-3 overflow-hidden ml-1">
|
||||
<div class="w-8 h-8 bg-highlight rounded flex items-center justify-center shrink-0 shadow-sm shadow-highlight/20">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#3a2800]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9l-.707.707M12 18v3m4.95-4.95l.707.707M12 3c-4.418 0-8 3.582-8 8 0 2.209.895 4.209 2.343 5.657L12 21l5.657-5.343A7.994 7.994 0 0020 11c0-4.418-3.582-8-8-8z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span v-if="isSidebarOpen" class="font-serif font-black text-xl tracking-tight whitespace-nowrap text-white">RECRU<span class="text-accent italic px-0.5">IT</span></span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 py-6 px-3 space-y-1.5 overflow-y-auto custom-scrollbar">
|
||||
<Link
|
||||
:href="route('dashboard')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('dashboard') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Tableau de bord</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
:href="route('admin.candidates.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.candidates.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Candidats</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
:href="route('admin.quizzes.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.quizzes.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Quiz</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
:href="route('admin.job-positions.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.job-positions.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" 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>
|
||||
<span v-if="isSidebarOpen" class="truncate">Fiches de Poste</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
:href="route('admin.comparative')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.comparative') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Comparateur</span>
|
||||
</Link>
|
||||
|
||||
<div v-if="$page.props.auth.user.role === 'super_admin'" class="pt-4 pb-2">
|
||||
<div v-show="isSidebarOpen" class="px-3 text-[10px] font-black uppercase tracking-widest text-white/30">Configuration</div>
|
||||
<div v-show="!isSidebarOpen" class="h-[1px] w-8 mx-auto bg-white/10"></div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
:href="route('admin.tenants.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.tenants.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Structures</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
:href="route('admin.users.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.users.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Équipe SaaS</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
v-if="$page.props.auth.user.role === 'super_admin'"
|
||||
:href="route('admin.logs.index')"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-300 font-subtitle font-bold text-sm"
|
||||
:class="[route().current('admin.logs.*') ? 'bg-highlight text-[#3a2800] shadow-md shadow-highlight/20' : 'text-white/70 hover:bg-white/10 hover:text-white']"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||
</svg>
|
||||
<span v-if="isSidebarOpen" class="truncate">Logs de connexion</span>
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div class="p-4 border-t border-white/10 bg-primary/80">
|
||||
<button
|
||||
@click="isSidebarOpen = !isSidebarOpen"
|
||||
class="flex items-center justify-center w-full h-10 rounded-lg text-white/50 hover:bg-white/10 hover:text-white transition-all duration-300"
|
||||
>
|
||||
<svg v-if="isSidebarOpen" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<div v-show="isSidebarOpen" class="mt-4 text-[9px] font-bold uppercase tracking-widest text-[#3a7abf] text-center">
|
||||
App v{{ $page.props.app_version }}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col min-w-0 overflow-hidden bg-neutral">
|
||||
<header class="h-16 shrink-0 flex items-center justify-between px-8 bg-white border-b border-anthracite/5 shadow-sm z-10 relative">
|
||||
<div>
|
||||
<h2 v-if="$slots.header" class="font-serif font-black text-xl text-primary capitalize tracking-tight flex items-center gap-3">
|
||||
<div class="w-1.5 h-6 bg-highlight rounded-full hidden md:block"></div>
|
||||
<slot name="header" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<Dropdown align="right" width="48">
|
||||
<template #trigger>
|
||||
<button class="flex items-center gap-2 text-sm font-subtitle font-bold hover:text-primary transition-colors cursor-pointer py-2">
|
||||
<div class="w-8 h-8 rounded-full bg-sand flex items-center justify-center text-primary border border-primary/10">
|
||||
{{ $page.props.auth.user.name.charAt(0) }}
|
||||
</div>
|
||||
<span class="hidden md:block">{{ $page.props.auth.user.name }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-anthracite/40" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<DropdownLink :href="route('profile.edit')">Paramètres du profil</DropdownLink>
|
||||
<DropdownLink :href="route('admin.backup')" as="a" class="!text-sky font-bold" v-if="$page.props.auth.user.role === 'super_admin'">Sauvegarde Base de données</DropdownLink>
|
||||
<div class="border-t border-anthracite/5 my-1"></div>
|
||||
<DropdownLink :href="route('logout')" method="post" as="button" class="!text-accent font-bold">Se déconnecter</DropdownLink>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 overflow-y-auto p-4 md:p-8">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Firefox scrollbar config for sidebar */
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user