Minima CCNPA + simulateur modal + calculette

This commit is contained in:
Renaud 2025-10-19 21:19:42 +02:00
parent 5c36a4a8ee
commit f154173f2d
28 changed files with 8339 additions and 144 deletions

BIN
Minima CCNPA 2025 (1).xlsx Normal file

Binary file not shown.

View file

@ -11,65 +11,22 @@ import ArtistesCirqueContent from './artistes-cirque-data';
import EmploisNonArtistiquesContent from './emplois-non-artistiques-data';
import SimulateurContent from '@/components/simulateur/SimulateurContent';
import CalculatorComponent from '@/components/Calculator';
import { useDraggableModal } from '@/hooks/useDraggableModal';
export default function CCNEACPage() {
usePageTitle("Minima CCNEAC");
const [isSimulateurOpen, setIsSimulateurOpen] = useState(false);
const [isCalculatorOpen, setIsCalculatorOpen] = useState(false);
const [modalPosition, setModalPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const modalRef = React.useRef<HTMLDivElement>(null);
// Gestion du drag & drop du modal
const handleMouseDown = (e: React.MouseEvent) => {
if (!modalRef.current) return;
// Si c'est le premier drag, calculer la position réelle du modal
if (modalPosition.x === 0 && modalPosition.y === 0) {
const rect = modalRef.current.getBoundingClientRect();
setModalPosition({ x: rect.left, y: rect.top });
setDragOffset({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
} else {
setDragOffset({
x: e.clientX - modalPosition.x,
y: e.clientY - modalPosition.y,
});
}
setIsDragging(true);
};
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging) return;
const newX = e.clientX - dragOffset.x;
const newY = e.clientY - dragOffset.y;
setModalPosition({
x: newX,
y: newY,
});
};
const handleMouseUp = () => {
setIsDragging(false);
};
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, dragOffset]);
const { onPointerDown, onPointerMove, onPointerUp } = useDraggableModal(
modalRef,
setModalPosition,
{ constrainToViewport: true, disableIframeDuringDrag: true }
);
// Écouter les messages de l'iframe pour ouvrir la calculatrice
useEffect(() => {
@ -298,31 +255,23 @@ export default function CCNEACPage() {
{/* Modale compacte qui sort du bouton */}
{isSimulateurOpen && (
<>
{/* Overlay transparent pour fermer au clic */}
<div
className="fixed inset-0 z-40"
onClick={() => {
setIsSimulateurOpen(false);
setModalPosition({ x: 0, y: 0 }); // Réinitialiser la position
}}
/>
{/* Modale compacte déplaçable */}
<div
ref={modalRef}
className="fixed z-50 w-[500px] max-h-[600px] bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden animate-in slide-in-from-bottom-4 duration-300"
className="fixed z-50 w-[500px] max-h-[680px] bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden"
style={{
left: modalPosition.x !== 0 ? `${modalPosition.x}px` : 'auto',
top: modalPosition.y !== 0 ? `${modalPosition.y}px` : 'auto',
bottom: modalPosition.x === 0 && modalPosition.y === 0 ? '6rem' : 'auto',
right: modalPosition.x === 0 && modalPosition.y === 0 ? '1.5rem' : 'auto',
}}
onClick={(e) => e.stopPropagation()}
>
{/* Header draggable */}
<div
className="flex items-center justify-between px-4 py-3 border-b bg-gradient-to-r from-amber-50 to-orange-50 cursor-move select-none"
onMouseDown={handleMouseDown}
className="flex items-center justify-between px-4 py-3 border-b bg-gradient-to-r from-amber-50 to-orange-50 cursor-move select-none touch-none"
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
>
<h3 className="text-sm font-bold text-slate-900 flex items-center gap-2">
<Calculator className="w-4 h-4 text-amber-600" />
@ -335,7 +284,7 @@ export default function CCNEACPage() {
}}
className="p-1.5 rounded-lg hover:bg-slate-100 transition-colors"
aria-label="Fermer le simulateur"
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<X className="w-4 h-4 text-slate-600" />
</button>

View file

@ -0,0 +1,166 @@
"use client";
import React from 'react';
import { Mic, Info } from 'lucide-react';
function euro(n: number) {
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(n);
}
const InfoRow: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="flex items-start gap-2">
<div className="mt-0.5 flex-shrink-0 w-5 h-5 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center">
<Info className="w-3.5 h-3.5 text-slate-600" aria-hidden="true" />
</div>
<p className="text-sm text-slate-600">{children}</p>
</div>
);
export default function ArtistesInterpretes() {
return (
<div className="space-y-6">
{/* En-tête */}
<section className="rounded-2xl border bg-white p-6">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center">
<Mic className="w-6 h-6 text-white" />
</div>
<div className="flex-1 min-w-0">
<h2 className="text-xl font-semibold text-slate-900">Artistes Interprètes</h2>
<p className="text-sm text-slate-600">Salaires minima applicables au 1er janvier 2025 (Avenant n°20 du 29 novembre 2024, non étendu)</p>
<p className="text-xs text-slate-500 mt-1">Rémunération au cachet</p>
</div>
</div>
</section>
{/* Émissions dramatiques */}
<section className="rounded-2xl border overflow-hidden">
<div className="bg-indigo-600 text-white px-4 py-3 text-sm font-semibold">Émissions dramatiques (article 5.14.1)</div>
<div className="grid grid-cols-2 text-sm font-semibold text-white">
<div className="bg-indigo-600/90 px-4 py-2.5">Définition</div>
<div className="bg-indigo-600/90 px-4 py-2.5">Cachet</div>
</div>
{[{def:"Journée de répétition ou d'enregistrement",val:289.23},{def:'Journée unique',val:304.99},{def:"Prestation de lecture d'une durée inférieure à 4 heures",val:152.49}].map((r,idx)=> (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx%2? 'bg-slate-50':'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.val)}</div>
</div>
))}
</section>
{/* Émissions de variétés */}
<section className="rounded-2xl border overflow-hidden">
<div className="bg-indigo-600 text-white px-4 py-3 text-sm font-semibold">Émissions de variétés (article 5.14.2)</div>
<div className="px-4 py-2 bg-slate-50 text-sm font-semibold text-slate-800">Répétitions effectuées en dehors de la journée d'enregistrement</div>
<div className="grid grid-cols-2 text-sm font-semibold text-white">
<div className="bg-indigo-600/90 px-4 py-2.5">Définition</div>
<div className="bg-indigo-600/90 px-4 py-2.5">Cachet</div>
</div>
{[{def:"Répétition d'une durée inférieure ou égale à 4 heures",val:184.90},{def:"Répétition d'une durée supérieure à 4 heures",val:289.23}].map((r,idx)=> (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx%2? 'bg-slate-50':'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.val)}</div>
</div>
))}
<div className="grid grid-cols-2 text-sm">
<div className="border-t px-4 py-3 text-slate-900">Enregistrement</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(419.31)}</div>
</div>
</section>
{/* Émissions lyriques */}
<section className="rounded-2xl border overflow-hidden">
<div className="bg-indigo-600 text-white px-4 py-3 text-sm font-semibold">Émissions lyriques (article 5.14.3)</div>
<div className="px-4 py-2 bg-slate-50 text-sm font-semibold text-slate-800">Répétition ou enregistrement</div>
<div className="grid grid-cols-2 text-sm font-semibold text-white">
<div className="bg-indigo-600/90 px-4 py-2.5">Définition</div>
<div className="bg-indigo-600/90 px-4 py-2.5">Cachet</div>
</div>
{[{def:'Soliste',val:432.86},{def:'Artistes des chœurs',val:289.23}].map((r,idx)=> (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx%2? 'bg-slate-50':'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.val)}</div>
</div>
))}
<div className="px-4 py-2 bg-slate-50 text-sm font-semibold text-slate-800">Préparation ou déchiffrage (3 heures maximum)</div>
{[{def:'Soliste',val:165.96},{def:"Artistes des chœurs",val:110.89}].map((r,idx)=> (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx%2? 'bg-slate-50':'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.val)}</div>
</div>
))}
</section>
{/* Émissions chorégraphiques */}
<section className="rounded-2xl border overflow-hidden">
<div className="bg-indigo-600 text-white px-4 py-3 text-sm font-semibold">Émissions chorégraphiques (article 5.14.4)</div>
<div className="px-4 py-2 bg-slate-50 text-sm font-semibold text-slate-800">Répétition ou enregistrement (6h de travail effectif au maximum)</div>
{[{def:'Soliste',val:432.86},{def:'Corps de ballet',val:289.23}].map((r,idx)=> (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx%2? 'bg-slate-50':'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.val)}</div>
</div>
))}
</section>
{/* Reportages / Actualité */}
<section className="rounded-2xl border overflow-hidden">
<div className="bg-indigo-600 text-white px-4 py-3 text-sm font-semibold">Reportages / Actualité</div>
<div className="grid grid-cols-2 text-sm font-semibold text-white">
<div className="bg-indigo-600/90 px-4 py-2.5">Définition</div>
<div className="bg-indigo-600/90 px-4 py-2.5">Cachet</div>
</div>
<div className="grid grid-cols-2 text-sm">
<div className="border-t px-4 py-3 text-slate-900">Reportage (art. 6.2.1.b, pas de gré à gré)</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(73.64)}</div>
</div>
<div className="grid grid-cols-2 text-sm">
<div className="border-t px-4 py-3 text-slate-900">Prestation d'actualité (art. 6.3.1, pas de gré à gré)</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(170.16)}</div>
</div>
</section>
{/* Indemnités de costumes */}
<section className="rounded-2xl border overflow-hidden">
<div className="bg-indigo-600 text-white px-4 py-3 text-sm font-semibold">Indemnités de costumes (article 5.13)</div>
<div className="px-4 py-2 bg-slate-50 text-sm font-semibold text-slate-800">Indemnités visées à l'article 5.13.1</div>
<div className="grid grid-cols-2 text-sm font-semibold text-white">
<div className="bg-indigo-600/90 px-4 py-2.5">Définition</div>
<div className="bg-indigo-600/90 px-4 py-2.5">Montant</div>
</div>
{[{def:'Engagement pour une journée unique — Tenue de ville',val:18.34},{def:'Engagement pour une journée unique — Tenue de soirée',val:30.10},{def:'Engagement pour plusieurs jours — Tenue de ville',val:14.67},{def:'Engagement pour plusieurs jours — Tenue de soirée',val:24.77}].map((r,idx)=> (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx%2? 'bg-slate-50':'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.val)}</div>
</div>
))}
<div className="px-4 py-2 bg-slate-50 text-sm font-semibold text-slate-800">Indemnités visées à l'article 5.13.2</div>
{[{def:'Homme — pourpoint',val:14.56},{def:'Femme — Tutu court',val:14.56},{def:'Femme — Tutu romantique',val:24.77},{def:'Chaussons',val:5.60}].map((r,idx)=> (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx%2? 'bg-slate-50':'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.val)}</div>
</div>
))}
</section>
{/* Notes et définitions */}
<section className="rounded-2xl border bg-white p-6 space-y-3">
<InfoRow>
La durée du travail journalière est de <strong>9 heures</strong> (6 heures pour les mineurs de moins de 16 ans), y compris le temps passé à l'habillage et au maquillage, dans la limite d'une heure. La durée du travail hebdomadaire est de <strong>45 heures</strong> (5 × 9 heures) pour les adultes.
</InfoRow>
<InfoRow>
<strong>Émissions dramatiques</strong> : artistes engagés pour la réalisation télévisuelle d'œuvres ou extraits d'œuvres dramatiques (...), y compris artistes sans texte à respecter (voix hors champ, lectures de commentaires).
</InfoRow>
<InfoRow>
<strong>Émissions chorégraphiques</strong> : réalisations télévisuelles d'œuvres chorégraphiques (suite de pas/enchaînements réglés), à l'exclusion des artistes d'interprétation d'un texte parlé/chanté ou d'un numéro de variétés.
</InfoRow>
<InfoRow>
<strong>Émissions lyriques</strong> : réalisations télévisuelles d'œuvres lyriques ou émissions avec extraits lyriques ; inclut artistes des chœurs à l'image si intégrés à l'action dramatique. À l'exclusion des artistes d'interprétation d'un texte parlé/numéro de variétés ou de danse (ces derniers relèvent des dispositions des émissions dramatiques).
</InfoRow>
<InfoRow>
<strong>Émissions de variétés</strong> : prestations dans des conditions autres que celles prévues pour les émissions dramatiques, lyriques ou chorégraphiques, à l'exclusion des artistes chorégraphiques.
</InfoRow>
</section>
</div>
);
}

View file

@ -0,0 +1,109 @@
"use client";
import React from 'react';
import { Music, Info } from 'lucide-react';
function euro(n: number) {
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(n);
}
const MUSICIENS_CACHETS = [
{ def: 'Cachet initial (avec un mode) pour un service de 3 heures', montant: 106.05 },
{ def: 'Cachet initial (avec un mode) pour un service de 4 heures', montant: 137.87 },
{ def: 'Cachet initial (avec un mode) pour un engagement à la journée (isolée, enregistrement)', montant: 228.02 },
{ def: 'Cachet initial (avec un mode) pour un engagement à la journée (3 journées isolées ou 2 journées consécutives sur 7 jours)', montant: 217.41 },
{ def: 'Cachet initial (avec un mode) pour un engagement à la journée (5 journées isolées ou 3 journées consécutives sur 7 jours)', montant: 190.89 },
];
const MUSICIENS_ABATTEMENTS = [
{ seuil: '+ 10 musiciens', value: '-10%' },
{ seuil: '+ 20 musiciens', value: '-15%' },
{ seuil: '+ 30 musiciens', value: '-20%' },
{ seuil: '+ 40 musiciens', value: '-25%' },
];
const MUSICIENS_REPETITIONS = [
{ def: 'Cachet pour un service de trois heures', montant: 63.63 },
{ def: 'Cachet pour un double service de trois heures', montant: 106.05 },
];
const InfoRow: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="flex items-start gap-2">
<div className="mt-0.5 flex-shrink-0 w-5 h-5 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center">
<Info className="w-3.5 h-3.5 text-slate-600" aria-hidden="true" />
</div>
<p className="text-sm text-slate-600">{children}</p>
</div>
);
export default function ArtistesMusiciens() {
return (
<div className="space-y-6">
{/* En-tête */}
<section className="rounded-2xl border bg-white p-6">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-gradient-to-br from-fuchsia-500 to-pink-600 flex items-center justify-center">
<Music className="w-6 h-6 text-white" />
</div>
<div className="flex-1 min-w-0">
<h2 className="text-xl font-semibold text-slate-900">Artistes Musiciens</h2>
<p className="text-sm text-slate-600">Salaires applicables au 1er janvier 2025 (Avenant n°20 du 29 novembre 2024, non étendu)</p>
</div>
</div>
</section>
{/* Cachets minima */}
<section className="rounded-2xl border overflow-hidden">
<div className="grid grid-cols-2 text-sm font-semibold text-white">
<div className="bg-fuchsia-600 px-4 py-3">Définition du cachet</div>
<div className="bg-fuchsia-600 px-4 py-3">Cachets minima</div>
</div>
{MUSICIENS_CACHETS.map((row, idx) => (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx % 2 === 1 ? 'bg-slate-50' : 'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{row.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(row.montant)}</div>
</div>
))}
</section>
{/* Abattement pour ensemble */}
<section className="rounded-2xl border bg-white p-4">
<div className="grid md:grid-cols-2 gap-4 items-start">
<div>
<h4 className="text-sm font-semibold text-slate-900 mb-1">Abattement pour ensemble</h4>
<p className="text-sm text-slate-600">
En cas d'interprétation d'ensemble, abattement appliqué sur les cachets définis ci-avant en fonction du nombre de musiciens participant à l'ensemble.
</p>
</div>
<div className="rounded-xl border overflow-hidden">
<div className="grid grid-cols-2 text-xs font-semibold text-white">
<div className="bg-fuchsia-600 px-3 py-2">Seuil</div>
<div className="bg-fuchsia-600 px-3 py-2">Abattement</div>
</div>
{MUSICIENS_ABATTEMENTS.map((a, i) => (
<div key={i} className={`grid grid-cols-2 text-sm ${i % 2 === 1 ? 'bg-slate-50' : 'bg-white'}`}>
<div className="border-t px-3 py-2 text-slate-900">{a.seuil}</div>
<div className="border-t px-3 py-2 text-slate-900">{a.value}</div>
</div>
))}
</div>
</div>
</section>
{/* Cachets répétitions */}
<section className="rounded-2xl border overflow-hidden">
<div className="bg-slate-50 px-4 py-2 text-sm font-semibold text-slate-800">Cachet pour répétitions</div>
<div className="grid grid-cols-2 text-sm font-semibold text-white">
<div className="bg-fuchsia-600 px-4 py-3">Définition</div>
<div className="bg-fuchsia-600 px-4 py-3">Cachet</div>
</div>
{MUSICIENS_REPETITIONS.map((r, idx) => (
<div key={idx} className={`grid grid-cols-2 text-sm ${idx % 2 === 1 ? 'bg-slate-50' : 'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{r.def}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(r.montant)}</div>
</div>
))}
</section>
</div>
);
}

View file

@ -0,0 +1,615 @@
"use client";
import React from 'react';
import { Briefcase, ShoppingBag } from 'lucide-react';
const parseEuro = (str: string) => {
if (!str || str === '-') return null;
const cleaned = str.replace(/\s/g, '').replace('€', '').replace(',', '.');
return parseFloat(cleaned);
};
const euro = (n: number | null) => {
if (n === null) return '-';
return new Intl.NumberFormat('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n) + ' €';
};
export default function CategorieAContent() {
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-blue-50 to-cyan-50 p-4">
<h2 className="text-lg font-semibold text-blue-900 mb-1">
Catégorie A - Personnel d'administration et commercial
</h2>
<p className="text-sm text-blue-700">
Minima conventionnels pour le personnel administratif et commercial de la production audiovisuelle
</p>
<p className="text-xs text-blue-600 mt-2">
Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Hors niveau */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-slate-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-slate-500"></span>
<Briefcase className="w-5 h-5" />
Hors niveau
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-slate-50">
<th className="text-left p-3 text-slate-900 font-bold border border-slate-200">Emplois</th>
<th className="text-center p-3 text-slate-900 font-bold border border-slate-200 bg-slate-100">Salaire minimum</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-slate-50/30">
<td className="p-3 border border-slate-200">Producteur</td>
<td className="text-center p-3 font-semibold border border-slate-200">{euro(null)}</td>
</tr>
<tr className="hover:bg-slate-50/30">
<td className="p-3 border border-slate-200">Directeur général (non mandataire social)</td>
<td className="text-center p-3 font-semibold border border-slate-200">{euro(null)}</td>
</tr>
<tr className="hover:bg-slate-50/30">
<td className="p-3 border border-slate-200">Directeur général délégué (non mandataire social)</td>
<td className="text-center p-3 font-semibold border border-slate-200">{euro(null)}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau I - Administration */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-blue-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-blue-500"></span>
<Briefcase className="w-5 h-5" />
Niveau I - Filière Administration
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-blue-50">
<th className="text-left p-3 text-blue-900 font-bold border border-blue-200">Emplois</th>
<th className="text-center p-3 text-blue-900 font-bold border border-blue-200 bg-blue-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-blue-50/30">
<td className="p-3 border border-blue-200">Délégué Général</td>
<td className="text-center p-3 font-bold text-blue-900 border border-blue-200 bg-blue-50">{euro(parseEuro("3 314,29 €"))}</td>
</tr>
<tr className="hover:bg-blue-50/30">
<td className="p-3 border border-blue-200">Directeur général adjoint</td>
<td className="text-center p-3 font-bold text-blue-900 border border-blue-200 bg-blue-50">{euro(parseEuro("3 314,29 €"))}</td>
</tr>
<tr className="hover:bg-blue-50/30">
<td className="p-3 border border-blue-200">Directeur des productions</td>
<td className="text-center p-3 font-bold text-blue-900 border border-blue-200 bg-blue-50">{euro(parseEuro("3 314,29 €"))}</td>
</tr>
<tr className="hover:bg-blue-50/30">
<td className="p-3 border border-blue-200">Directeur des programmes</td>
<td className="text-center p-3 font-bold text-blue-900 border border-blue-200 bg-blue-50">{euro(parseEuro("3 314,29 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau II - Administration */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-cyan-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-cyan-500"></span>
<Briefcase className="w-5 h-5" />
Niveau II - Filière Administration
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-cyan-50">
<th className="text-left p-3 text-cyan-900 font-bold border border-cyan-200">Emplois</th>
<th className="text-center p-3 text-cyan-900 font-bold border border-cyan-200 bg-cyan-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur des jeux</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("3 162,15 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Secrétaire général</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("3 013,00 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur administratif et financier</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("3 013,00 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur financier</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur juridique</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur technique</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur des ressources humaines</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur littéraire</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur du développement</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur informatique</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur de la Comptabilité</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-cyan-50/30">
<td className="p-3 border border-cyan-200">Directeur de la communication</td>
<td className="text-center p-3 font-bold text-cyan-900 border border-cyan-200 bg-cyan-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau II - Commercial et édition */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-emerald-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-emerald-500"></span>
<ShoppingBag className="w-5 h-5" />
Niveau II - Filière Commercial et édition
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-emerald-50">
<th className="text-left p-3 text-emerald-900 font-bold border border-emerald-200">Emplois</th>
<th className="text-center p-3 text-emerald-900 font-bold border border-emerald-200 bg-emerald-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-emerald-50/30">
<td className="p-3 border border-emerald-200">Directeur du Pôle Edition - Distribution</td>
<td className="text-center p-3 font-bold text-emerald-900 border border-emerald-200 bg-emerald-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-emerald-50/30">
<td className="p-3 border border-emerald-200">Directeur multimedia</td>
<td className="text-center p-3 font-bold text-emerald-900 border border-emerald-200 bg-emerald-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-emerald-50/30">
<td className="p-3 border border-emerald-200">Directeur produits dérivés</td>
<td className="text-center p-3 font-bold text-emerald-900 border border-emerald-200 bg-emerald-50">{euro(parseEuro("2 892,47 €"))}</td>
</tr>
<tr className="hover:bg-emerald-50/30">
<td className="p-3 border border-emerald-200">Directeur commercial</td>
<td className="text-center p-3 font-bold text-emerald-900 border border-emerald-200 bg-emerald-50">{euro(parseEuro("2 651,44 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau IIIA - Administration */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-sky-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-sky-500"></span>
<Briefcase className="w-5 h-5" />
Niveau IIIA - Filière Administration
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-sky-50">
<th className="text-left p-3 text-sky-900 font-bold border border-sky-200">Emplois</th>
<th className="text-center p-3 text-sky-900 font-bold border border-sky-200 bg-sky-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable administratif et financier</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 651,44 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Chef comptable</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 530,92 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable des ressources humaines</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 530,92 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable du développement</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 530,92 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable informatique</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 530,92 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable juridique</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 530,92 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Contrôleur de gestion</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 410,40 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable de la trésorerie</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 410,40 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable de la communication</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 410,40 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable de la paie</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 339,74 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable technique</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 289,87 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Responsable des services généraux</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 289,87 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Chargé de mission</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 289,87 €"))}</td>
</tr>
<tr className="hover:bg-sky-50/30">
<td className="p-3 border border-sky-200">Attaché de direction</td>
<td className="text-center p-3 font-bold text-sky-900 border border-sky-200 bg-sky-50">{euro(parseEuro("2 171,66 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau IIIA - Commercial et édition */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-teal-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-teal-500"></span>
<ShoppingBag className="w-5 h-5" />
Niveau IIIA - Filière Commercial et édition
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-teal-50">
<th className="text-left p-3 text-teal-900 font-bold border border-teal-200">Emplois</th>
<th className="text-center p-3 text-teal-900 font-bold border border-teal-200 bg-teal-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-teal-50/30">
<td className="p-3 border border-teal-200">Responsable des sites web/multimedia</td>
<td className="text-center p-3 font-bold text-teal-900 border border-teal-200 bg-teal-50">{euro(parseEuro("2 410,40 €"))}</td>
</tr>
<tr className="hover:bg-teal-50/30">
<td className="p-3 border border-teal-200">Responsable des ventes</td>
<td className="text-center p-3 font-bold text-teal-900 border border-teal-200 bg-teal-50">{euro(parseEuro("2 289,87 €"))}</td>
</tr>
<tr className="hover:bg-teal-50/30">
<td className="p-3 border border-teal-200">Responsable des produits dérivés</td>
<td className="text-center p-3 font-bold text-teal-900 border border-teal-200 bg-teal-50">{euro(parseEuro("2 289,87 €"))}</td>
</tr>
<tr className="hover:bg-teal-50/30">
<td className="p-3 border border-teal-200">Responsable acquisitions</td>
<td className="text-center p-3 font-bold text-teal-900 border border-teal-200 bg-teal-50">{euro(parseEuro("2 289,87 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau IIIB - Administration */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-indigo-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-indigo-500"></span>
<Briefcase className="w-5 h-5" />
Niveau IIIB - Filière Administration
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-indigo-50">
<th className="text-left p-3 text-indigo-900 font-bold border border-indigo-200">Emplois</th>
<th className="text-center p-3 text-indigo-900 font-bold border border-indigo-200 bg-indigo-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Responsable de la comptabilité</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 169,36 €"))}</td>
</tr>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Responsable de l'administration du personnel</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 169,36 €"))}</td>
</tr>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Attaché de presse</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 048,84 €"))}</td>
</tr>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Collaborateur juridique</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 048,84 €"))}</td>
</tr>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Contrôleur de gestion junior</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 048,84 €"))}</td>
</tr>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Informaticien</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 048,84 €"))}</td>
</tr>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Responsable d'exploitation</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 048,84 €"))}</td>
</tr>
<tr className="hover:bg-indigo-50/30">
<td className="p-3 border border-indigo-200">Chargé d'étude</td>
<td className="text-center p-3 font-bold text-indigo-900 border border-indigo-200 bg-indigo-50">{euro(parseEuro("2 028,56 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau IIIB - Commercial et édition */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-violet-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-violet-500"></span>
<ShoppingBag className="w-5 h-5" />
Niveau IIIB - Filière Commercial et édition
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-violet-50">
<th className="text-left p-3 text-violet-900 font-bold border border-violet-200">Emplois</th>
<th className="text-center p-3 text-violet-900 font-bold border border-violet-200 bg-violet-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-violet-50/30">
<td className="p-3 border border-violet-200">Responsable des supports</td>
<td className="text-center p-3 font-bold text-violet-900 border border-violet-200 bg-violet-50">{euro(parseEuro("2 048,84 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau IV - Administration */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-purple-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-purple-500"></span>
<Briefcase className="w-5 h-5" />
Niveau IV - Filière Administration
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-purple-50">
<th className="text-left p-3 text-purple-900 font-bold border border-purple-200">Emplois</th>
<th className="text-center p-3 text-purple-900 font-bold border border-purple-200 bg-purple-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-purple-50/30">
<td className="p-3 border border-purple-200">Comptable</td>
<td className="text-center p-3 font-bold text-purple-900 border border-purple-200 bg-purple-50">{euro(parseEuro("1 988,58 €"))}</td>
</tr>
<tr className="hover:bg-purple-50/30">
<td className="p-3 border border-purple-200">Gestionnaire paie</td>
<td className="text-center p-3 font-bold text-purple-900 border border-purple-200 bg-purple-50">{euro(parseEuro("1 988,58 €"))}</td>
</tr>
<tr className="hover:bg-purple-50/30">
<td className="p-3 border border-purple-200">Webmestre</td>
<td className="text-center p-3 font-bold text-purple-900 border border-purple-200 bg-purple-50">{euro(parseEuro("1 928,32 €"))}</td>
</tr>
<tr className="hover:bg-purple-50/30">
<td className="p-3 border border-purple-200">Chargé des services généraux</td>
<td className="text-center p-3 font-bold text-purple-900 border border-purple-200 bg-purple-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-purple-50/30">
<td className="p-3 border border-purple-200">Assistant de direction</td>
<td className="text-center p-3 font-bold text-purple-900 border border-purple-200 bg-purple-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-purple-50/30">
<td className="p-3 border border-purple-200">Assistant juridique</td>
<td className="text-center p-3 font-bold text-purple-900 border border-purple-200 bg-purple-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau IV - Commercial et édition */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-fuchsia-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-fuchsia-500"></span>
<ShoppingBag className="w-5 h-5" />
Niveau IV - Filière Commercial et édition
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-fuchsia-50">
<th className="text-left p-3 text-fuchsia-900 font-bold border border-fuchsia-200">Emplois</th>
<th className="text-center p-3 text-fuchsia-900 font-bold border border-fuchsia-200 bg-fuchsia-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-fuchsia-50/30">
<td className="p-3 border border-fuchsia-200">Gestionnaire des supports</td>
<td className="text-center p-3 font-bold text-fuchsia-900 border border-fuchsia-200 bg-fuchsia-50">{euro(parseEuro("1 909,23 €"))}</td>
</tr>
<tr className="hover:bg-fuchsia-50/30">
<td className="p-3 border border-fuchsia-200">Vendeur</td>
<td className="text-center p-3 font-bold text-fuchsia-900 border border-fuchsia-200 bg-fuchsia-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau V - Administration */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-pink-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-pink-500"></span>
<Briefcase className="w-5 h-5" />
Niveau V - Filière Administration
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-pink-50">
<th className="text-left p-3 text-pink-900 font-bold border border-pink-200">Emplois</th>
<th className="text-center p-3 text-pink-900 font-bold border border-pink-200 bg-pink-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-pink-50/30">
<td className="p-3 border border-pink-200">Secrétaire - assistant</td>
<td className="text-center p-3 font-bold text-pink-900 border border-pink-200 bg-pink-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-pink-50/30">
<td className="p-3 border border-pink-200">Secrétaire - standardiste</td>
<td className="text-center p-3 font-bold text-pink-900 border border-pink-200 bg-pink-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-pink-50/30">
<td className="p-3 border border-pink-200">Responsable d'entretien</td>
<td className="text-center p-3 font-bold text-pink-900 border border-pink-200 bg-pink-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-pink-50/30">
<td className="p-3 border border-pink-200">Assistant paye</td>
<td className="text-center p-3 font-bold text-pink-900 border border-pink-200 bg-pink-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-pink-50/30">
<td className="p-3 border border-pink-200">Assistant comptable</td>
<td className="text-center p-3 font-bold text-pink-900 border border-pink-200 bg-pink-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-pink-50/30">
<td className="p-3 border border-pink-200">Assistant de la communication</td>
<td className="text-center p-3 font-bold text-pink-900 border border-pink-200 bg-pink-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-pink-50/30">
<td className="p-3 border border-pink-200">Agent des services généraux</td>
<td className="text-center p-3 font-bold text-pink-900 border border-pink-200 bg-pink-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau V - Commercial et édition */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-rose-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-rose-500"></span>
<ShoppingBag className="w-5 h-5" />
Niveau V - Filière Commercial et édition
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-rose-50">
<th className="text-left p-3 text-rose-900 font-bold border border-rose-200">Emplois</th>
<th className="text-center p-3 text-rose-900 font-bold border border-rose-200 bg-rose-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-rose-50/30">
<td className="p-3 border border-rose-200">Assistant web / téléphonie / multimedia</td>
<td className="text-center p-3 font-bold text-rose-900 border border-rose-200 bg-rose-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-rose-50/30">
<td className="p-3 border border-rose-200">Assistant commercial</td>
<td className="text-center p-3 font-bold text-rose-900 border border-rose-200 bg-rose-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Niveau VI - Administration */}
<div className="rounded-xl border bg-white p-5">
<h3 className="text-base font-bold text-red-900 mb-4 flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-red-500"></span>
<Briefcase className="w-5 h-5" />
Niveau VI - Filière Administration
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse">
<thead>
<tr className="bg-red-50">
<th className="text-left p-3 text-red-900 font-bold border border-red-200">Emplois</th>
<th className="text-center p-3 text-red-900 font-bold border border-red-200 bg-red-100">Salaire minimum CDI/CDD</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-red-50/30">
<td className="p-3 border border-red-200">Hôtesse - standardiste</td>
<td className="text-center p-3 font-bold text-red-900 border border-red-200 bg-red-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-red-50/30">
<td className="p-3 border border-red-200">Chauffeur d'entreprise</td>
<td className="text-center p-3 font-bold text-red-900 border border-red-200 bg-red-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-red-50/30">
<td className="p-3 border border-red-200">Agent d'exploitation</td>
<td className="text-center p-3 font-bold text-red-900 border border-red-200 bg-red-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-red-50/30">
<td className="p-3 border border-red-200">Coursier</td>
<td className="text-center p-3 font-bold text-red-900 border border-red-200 bg-red-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-red-50/30">
<td className="p-3 border border-red-200">Gardien</td>
<td className="text-center p-3 font-bold text-red-900 border border-red-200 bg-red-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
<tr className="hover:bg-red-50/30">
<td className="p-3 border border-red-200">Agent d'entretien</td>
<td className="text-center p-3 font-bold text-red-900 border border-red-200 bg-red-50">{euro(parseEuro("1 828,83 €"))}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,799 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Sparkles, Film, ClipboardList, Search, ChevronDown, ChevronUp } from 'lucide-react';
const parseEuro = (str: string) => {
if (!str || str === '-') return null;
const cleaned = str.replace(/\s/g, '').replace('€', '').replace(',', '.');
return parseFloat(cleaned);
};
const euro = (n: number | null) => {
if (n === null) return '-';
return new Intl.NumberFormat('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n) + ' €';
};
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
const emploisData: Emploi[] = [
// Filière D - Maquillage & Coiffure
{
nom: "Blocker / Rigger",
filiere: "D",
niveau: "IV",
cddu: {
semaine35h: parseEuro("873,86 €"),
semaine39h: parseEuro("998,69 €"),
jour7h: parseEuro("194,19 €"),
jour8h: parseEuro("221,93 €"),
mois35h: parseEuro("3 320,66 €"),
mois39h: parseEuro("3 795,03 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chef électricien",
filiere: "D",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("989,68 €"),
semaine39h: parseEuro("1 131,06 €"),
jour7h: parseEuro("219,93 €"),
jour8h: parseEuro("251,35 €"),
mois35h: parseEuro("3 760,77 €"),
mois39h: parseEuro("4 298,01 €"),
},
cdi: parseEuro("2 289,87 €"),
},
{
nom: "Chef machiniste",
filiere: "D",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("989,68 €"),
semaine39h: parseEuro("1 131,06 €"),
jour7h: parseEuro("219,93 €"),
jour8h: parseEuro("251,35 €"),
mois35h: parseEuro("3 760,77 €"),
mois39h: parseEuro("4 298,01 €"),
},
cdi: parseEuro("2 289,87 €"),
},
{
nom: "Chef maquilleur",
filiere: "D",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("884,43 €"),
semaine39h: parseEuro("1 010,78 €"),
jour7h: parseEuro("196,54 €"),
jour8h: parseEuro("224,62 €"),
mois35h: parseEuro("3 360,84 €"),
mois39h: parseEuro("3 840,95 €"),
},
cdi: parseEuro("2 410,40 €"),
},
{
nom: "Coiffeur",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Coiffeur perruquier",
filiere: "D",
niveau: "IV",
cddu: {
semaine35h: parseEuro("893,19 €"),
semaine39h: parseEuro("1 020,79 €"),
jour7h: parseEuro("198,49 €"),
jour8h: parseEuro("226,84 €"),
mois35h: parseEuro("3 394,11 €"),
mois39h: parseEuro("3 878,98 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Conducteur de groupe",
filiere: "D",
niveau: "IV",
cddu: {
semaine35h: parseEuro("881,41 €"),
semaine39h: parseEuro("1 007,33 €"),
jour7h: parseEuro("195,87 €"),
jour8h: parseEuro("223,85 €"),
mois35h: parseEuro("3 349,36 €"),
mois39h: parseEuro("3 827,83 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Électricien / Éclairagiste",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("811,99 €"),
semaine39h: parseEuro("927,99 €"),
jour7h: parseEuro("180,44 €"),
jour8h: parseEuro("206,22 €"),
mois35h: parseEuro("3 085,55 €"),
mois39h: parseEuro("3 526,34 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Machiniste",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("811,99 €"),
semaine39h: parseEuro("927,99 €"),
jour7h: parseEuro("180,44 €"),
jour8h: parseEuro("206,22 €"),
mois35h: parseEuro("3 085,55 €"),
mois39h: parseEuro("3 526,34 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Maquilleur",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Maquilleur et coiffeur effets spéciaux",
filiere: "D",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("1 072,06 €"),
semaine39h: parseEuro("1 225,21 €"),
jour7h: parseEuro("238,24 €"),
jour8h: parseEuro("272,27 €"),
mois35h: parseEuro("4 073,83 €"),
mois39h: parseEuro("4 655,79 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Prothésiste",
filiere: "D",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("1 072,06 €"),
semaine39h: parseEuro("1 225,21 €"),
jour7h: parseEuro("238,24 €"),
jour8h: parseEuro("272,27 €"),
mois35h: parseEuro("4 073,83 €"),
mois39h: parseEuro("4 655,79 €"),
},
cdi: parseEuro("2 169,36 €"),
},
// Filière E - Post-production
{
nom: "Assistant de post-production",
filiere: "E",
niveau: "IV",
cddu: {
semaine35h: parseEuro("626,05 €"),
semaine39h: parseEuro("715,48 €"),
jour7h: parseEuro("139,12 €"),
jour8h: parseEuro("159,00 €"),
mois35h: parseEuro("2 378,97 €"),
mois39h: parseEuro("2 718,82 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant monteur",
filiere: "E",
niveau: "IV",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 928,32 €"),
},
{
nom: "Assistant monteur adjoint",
filiere: "E",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chargé de post-production",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 051,06 €"),
semaine39h: parseEuro("1 201,21 €"),
jour7h: parseEuro("233,57 €"),
jour8h: parseEuro("266,94 €"),
mois35h: parseEuro("3 994,02 €"),
mois39h: parseEuro("4 564,58 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Chef monteur",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 127,35 €"),
semaine39h: parseEuro("1 288,39 €"),
jour7h: parseEuro("250,52 €"),
jour8h: parseEuro("286,31 €"),
mois35h: parseEuro("4 283,91 €"),
mois39h: parseEuro("4 895,89 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Directeur de post-production",
filiere: "E",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 264,33 €"),
semaine39h: parseEuro("1 444,95 €"),
jour7h: parseEuro("280,96 €"),
jour8h: parseEuro("321,10 €"),
mois35h: parseEuro("4 804,45 €"),
mois39h: parseEuro("5 490,78 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Étalonneur",
filiere: "E",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("964,96 €"),
semaine39h: parseEuro("1 102,82 €"),
jour7h: parseEuro("214,44 €"),
jour8h: parseEuro("245,07 €"),
mois35h: parseEuro("3 666,86 €"),
mois39h: parseEuro("4 190,69 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Infographiste",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 051,06 €"),
semaine39h: parseEuro("1 201,21 €"),
jour7h: parseEuro("233,57 €"),
jour8h: parseEuro("266,94 €"),
mois35h: parseEuro("3 994,02 €"),
mois39h: parseEuro("4 564,58 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Mixeur",
filiere: "E",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 348,40 €"),
semaine39h: parseEuro("1 541,03 €"),
jour7h: parseEuro("299,65 €"),
jour8h: parseEuro("342,45 €"),
mois35h: parseEuro("5 123,93 €"),
mois39h: parseEuro("5 855,91 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Superviseur d'effets spéciaux postproduction",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 180,27 €"),
semaine39h: parseEuro("1 348,88 €"),
jour7h: parseEuro("262,28 €"),
jour8h: parseEuro("299,75 €"),
mois35h: parseEuro("4 485,04 €"),
mois39h: parseEuro("5 125,75 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Truquiste",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("979,97 €"),
semaine39h: parseEuro("1 119,97 €"),
jour7h: parseEuro("217,77 €"),
jour8h: parseEuro("248,88 €"),
mois35h: parseEuro("3 723,90 €"),
mois39h: parseEuro("4 255,88 €"),
},
cdi: parseEuro("2 530,92 €"),
},
// Filière F - Production
{
nom: "Administrateur de production",
filiere: "F",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("877,44 €"),
semaine39h: parseEuro("1 002,79 €"),
jour7h: parseEuro("194,99 €"),
jour8h: parseEuro("222,84 €"),
mois35h: parseEuro("3 334,28 €"),
mois39h: parseEuro("3 810,59 €"),
},
cdi: parseEuro("2 591,18 €"),
},
{
nom: "Assistant de production",
filiere: "F",
niveau: "IV",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Assistant de production adjoint",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant régisseur adjoint",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chargé de production",
filiere: "F",
niveau: "II",
cddu: {
semaine35h: parseEuro("962,49 €"),
semaine39h: parseEuro("1 099,99 €"),
jour7h: parseEuro("213,89 €"),
jour8h: parseEuro("244,44 €"),
mois35h: parseEuro("3 657,48 €"),
mois39h: parseEuro("4 179,96 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Chauffeur",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("482,48 €"),
semaine39h: parseEuro("551,41 €"),
jour7h: parseEuro("107,22 €"),
jour8h: parseEuro("122,53 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Comptable de production",
filiere: "F",
niveau: "IV",
cddu: {
semaine35h: parseEuro("750,78 €"),
semaine39h: parseEuro("858,04 €"),
jour7h: parseEuro("166,84 €"),
jour8h: parseEuro("190,68 €"),
mois35h: parseEuro("2 852,98 €"),
mois39h: parseEuro("3 260,54 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Directeur de production",
filiere: "F",
niveau: "I",
cddu: {
semaine35h: parseEuro("1 532,22 €"),
semaine39h: parseEuro("1 751,11 €"),
jour7h: parseEuro("340,49 €"),
jour8h: parseEuro("389,14 €"),
mois35h: parseEuro("5 822,44 €"),
mois39h: parseEuro("6 654,20 €"),
},
cdi: parseEuro("3 314,29 €"),
},
{
nom: "Producteur exécutif",
filiere: "F",
niveau: "HN",
cddu: {
semaine35h: null,
semaine39h: null,
jour7h: null,
jour8h: null,
mois35h: null,
mois39h: null,
},
cdi: null,
},
{
nom: "Régisseur / Responsable des repérages",
filiere: "F",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("830,81 €"),
semaine39h: parseEuro("949,50 €"),
jour7h: parseEuro("184,63 €"),
jour8h: parseEuro("211,00 €"),
mois35h: parseEuro("3 157,09 €"),
mois39h: parseEuro("3 608,09 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Régisseur adjoint",
filiere: "F",
niveau: "IV",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Régisseur général",
filiere: "F",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("962,49 €"),
semaine39h: parseEuro("1 099,99 €"),
jour7h: parseEuro("213,89 €"),
jour8h: parseEuro("244,44 €"),
mois35h: parseEuro("3 657,48 €"),
mois39h: parseEuro("4 179,96 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Responsable des enfants",
filiere: "F",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("746,08 €"),
semaine39h: parseEuro("852,66 €"),
jour7h: parseEuro("165,80 €"),
jour8h: parseEuro("189,48 €"),
mois35h: parseEuro("2 835,09 €"),
mois39h: parseEuro("3 240,10 €"),
},
cdi: parseEuro("2 048,84 €"),
},
{
nom: "Secrétaire de production",
filiere: "F",
niveau: "V",
cddu: {
semaine35h: parseEuro("626,05 €"),
semaine39h: parseEuro("715,48 €"),
jour7h: parseEuro("139,12 €"),
jour8h: parseEuro("159,00 €"),
mois35h: parseEuro("2 378,97 €"),
mois39h: parseEuro("2 718,82 €"),
},
cdi: parseEuro("1 828,83 €"),
},
];
const filieres = [
{ code: 'D', nom: 'Plateaux et tournage', icon: Sparkles, color: 'pink' },
{ code: 'E', nom: 'Postproduction', icon: Film, color: 'indigo' },
{ code: 'F', nom: 'Production', icon: ClipboardList, color: 'green' },
];
const colorClasses = {
pink: {
bg: 'bg-pink-50',
border: 'border-pink-200',
text: 'text-pink-900',
hover: 'hover:bg-pink-100',
gradient: 'from-pink-500 to-rose-600',
ring: 'ring-pink-500',
},
indigo: {
bg: 'bg-indigo-50',
border: 'border-indigo-200',
text: 'text-indigo-900',
hover: 'hover:bg-indigo-100',
gradient: 'from-indigo-500 to-purple-600',
ring: 'ring-indigo-500',
},
green: {
bg: 'bg-green-50',
border: 'border-green-200',
text: 'text-green-900',
hover: 'hover:bg-green-100',
gradient: 'from-green-500 to-emerald-600',
ring: 'ring-green-500',
},
};
interface EmploiCardProps {
emploi: Emploi;
color: keyof typeof colorClasses;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border ${colors.border} bg-white overflow-hidden transition-all duration-200 hover:shadow-md`}>
{/* Header */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className={`w-full px-4 py-3 flex items-center justify-between ${colors.hover} transition-colors`}
>
<div className="flex items-center gap-3">
<span className={`inline-flex items-center px-2 py-1 rounded-md text-xs font-semibold ${colors.bg} ${colors.text}`}>
Niveau {emploi.niveau}
</span>
<h3 className="text-sm font-semibold text-slate-900">{emploi.nom}</h3>
</div>
{isExpanded ? (
<ChevronUp className="w-4 h-4 text-slate-400" />
) : (
<ChevronDown className="w-4 h-4 text-slate-400" />
)}
</button>
{/* Content */}
{isExpanded && (
<div className="border-t border-slate-100 p-4 space-y-4">
{/* Tabs */}
<div className="flex gap-2">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Content based on tab */}
{activeTab === 'cdi' ? (
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs text-slate-600 mb-2">Salaire mensuel minimum</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
</div>
) : (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
interface CategorieBFictionDataPart2Props {
activeFiliere: string;
}
export default function CategorieBFictionDataPart2({ activeFiliere }: CategorieBFictionDataPart2Props) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Déterminer la couleur en fonction de la filière
const getColorForFiliere = (filiereCode: string): keyof typeof colorClasses => {
const filiere = filieres.find(f => f.code === filiereCode);
return (filiere?.color as keyof typeof colorClasses) || 'blue';
};
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-orange-50 to-amber-50 p-4">
<h2 className="text-lg font-semibold text-orange-900 mb-1">
Filières D, E et F
</h2>
<p className="text-xs text-orange-600 mt-2">
{emploisData.length} emplois - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Rechercher un emploi (nom ou filière D/E/F)..."
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-cyan-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={getColorForFiliere(emploi.filiere)}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,603 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Clapperboard, Volume2, Globe, Search, ChevronDown, ChevronUp } from 'lucide-react';
const parseEuro = (str: string) => {
if (!str || str === '-') return null;
const cleaned = str.replace(/\s/g, '').replace('€', '').replace(',', '.');
return parseFloat(cleaned);
};
const euro = (n: number | null) => {
if (n === null) return '-';
return new Intl.NumberFormat('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n) + ' €';
};
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
const emploisData: Emploi[] = [
// Filière G - Réalisation
{
nom: "1er assistant réalisateur",
filiere: "G",
niveau: "II",
cddu: {
semaine35h: parseEuro("962,49 €"),
semaine39h: parseEuro("1 099,99 €"),
jour7h: parseEuro("213,89 €"),
jour8h: parseEuro("244,44 €"),
mois35h: parseEuro("3 657,48 €"),
mois39h: parseEuro("4 179,96 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "2ème assistant réalisateur",
filiere: "G",
niveau: "IV",
cddu: {
semaine35h: parseEuro("804,93 €"),
semaine39h: parseEuro("919,92 €"),
jour7h: parseEuro("178,87 €"),
jour8h: parseEuro("204,43 €"),
mois35h: parseEuro("3 058,73 €"),
mois39h: parseEuro("3 495,68 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Assistant réalisateur adjoint",
filiere: "G",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant scripte adjoint",
filiere: "G",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Conseiller technique à la réalisation",
filiere: "G",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 051,06 €"),
semaine39h: parseEuro("1 201,21 €"),
jour7h: parseEuro("233,57 €"),
jour8h: parseEuro("266,94 €"),
mois35h: parseEuro("3 994,02 €"),
mois39h: parseEuro("4 564,58 €"),
},
cdi: parseEuro("3 013,00 €"),
},
{
nom: "Répétiteur",
filiere: "G",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("746,08 €"),
semaine39h: parseEuro("852,66 €"),
jour7h: parseEuro("165,80 €"),
jour8h: parseEuro("189,48 €"),
mois35h: parseEuro("2 835,09 €"),
mois39h: parseEuro("3 240,10 €"),
},
cdi: parseEuro("2 048,84 €"),
},
{
nom: "Scripte",
filiere: "G",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("962,49 €"),
semaine39h: parseEuro("1 099,99 €"),
jour7h: parseEuro("213,89 €"),
jour8h: parseEuro("244,44 €"),
mois35h: parseEuro("3 657,48 €"),
mois39h: parseEuro("4 179,96 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Storyboarder",
filiere: "G",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("894,37 €"),
semaine39h: parseEuro("1 022,14 €"),
jour7h: parseEuro("198,75 €"),
jour8h: parseEuro("227,14 €"),
mois35h: parseEuro("3 398,61 €"),
mois39h: parseEuro("3 884,11 €"),
},
cdi: parseEuro("2 169,36 €"),
},
// Filière H - Son
{
nom: "Assistant son",
filiere: "H",
niveau: "IV",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 928,32 €"),
},
{
nom: "Assistant son adjoint",
filiere: "H",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Bruiteur",
filiere: "H",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 051,06 €"),
semaine39h: parseEuro("1 201,21 €"),
jour7h: parseEuro("233,57 €"),
jour8h: parseEuro("266,94 €"),
mois35h: parseEuro("3 994,02 €"),
mois39h: parseEuro("4 564,58 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Chef OPS / Ingénieur du son",
filiere: "H",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 212,78 €"),
semaine39h: parseEuro("1 386,03 €"),
jour7h: parseEuro("269,51 €"),
jour8h: parseEuro("308,01 €"),
mois35h: parseEuro("4 608,56 €"),
mois39h: parseEuro("5 266,91 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Perchiste / 1er assistant son",
filiere: "H",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("882,10 €"),
semaine39h: parseEuro("1 008,12 €"),
jour7h: parseEuro("196,02 €"),
jour8h: parseEuro("224,03 €"),
mois35h: parseEuro("3 351,98 €"),
mois39h: parseEuro("3 830,83 €"),
},
cdi: parseEuro("2 410,40 €"),
},
// Filière I - Web
{
nom: "Assistant technique web",
filiere: "I",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 006,02 €"),
mois39h: parseEuro("2 292,59 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Concepteur de programme web",
filiere: "I",
niveau: "I",
cddu: {
semaine35h: parseEuro("785,78 €"),
semaine39h: parseEuro("898,03 €"),
jour7h: parseEuro("174,62 €"),
jour8h: parseEuro("199,56 €"),
mois35h: parseEuro("3 402,43 €"),
mois39h: parseEuro("3 888,47 €"),
},
cdi: parseEuro("2 969,12 €"),
},
{
nom: "Coordinateur de diffusion web",
filiere: "I",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("519,92 €"),
semaine39h: parseEuro("594,19 €"),
jour7h: parseEuro("115,54 €"),
jour8h: parseEuro("132,04 €"),
mois35h: parseEuro("2 251,23 €"),
mois39h: parseEuro("2 572,83 €"),
},
cdi: parseEuro("1 959,62 €"),
},
{
nom: "Coordinateur de production web",
filiere: "I",
niveau: "II",
cddu: {
semaine35h: parseEuro("590,82 €"),
semaine39h: parseEuro("675,22 €"),
jour7h: parseEuro("131,29 €"),
jour8h: parseEuro("150,05 €"),
mois35h: parseEuro("2 558,24 €"),
mois39h: parseEuro("2 923,70 €"),
},
cdi: parseEuro("2 197,15 €"),
},
{
nom: "Designer web",
filiere: "I",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("543,55 €"),
semaine39h: parseEuro("621,20 €"),
jour7h: parseEuro("120,79 €"),
jour8h: parseEuro("138,05 €"),
mois35h: parseEuro("2 353,59 €"),
mois39h: parseEuro("2 689,80 €"),
},
cdi: parseEuro("2 019,01 €"),
},
{
nom: "Editeur artistique web",
filiere: "I",
niveau: "IV",
cddu: {
semaine35h: parseEuro("502,19 €"),
semaine39h: parseEuro("573,93 €"),
jour7h: parseEuro("111,60 €"),
jour8h: parseEuro("127,54 €"),
mois35h: parseEuro("2 174,48 €"),
mois39h: parseEuro("2 485,11 €"),
},
cdi: parseEuro("1 900,24 €"),
},
{
nom: "Gestionnaire de diffusion internet (traffic manager)",
filiere: "I",
niveau: "V",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 006,02 €"),
mois39h: parseEuro("2 292,59 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Opérateur web / Opérateur multicam web",
filiere: "I",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("567,18 €"),
semaine39h: parseEuro("648,21 €"),
jour7h: parseEuro("126,04 €"),
jour8h: parseEuro("144,05 €"),
mois35h: parseEuro("2 455,89 €"),
mois39h: parseEuro("2 806,72 €"),
},
cdi: parseEuro("2 137,77 €"),
},
{
nom: "Technicien de développement web",
filiere: "I",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("519,92 €"),
semaine39h: parseEuro("594,19 €"),
jour7h: parseEuro("115,54 €"),
jour8h: parseEuro("132,04 €"),
mois35h: parseEuro("2 251,23 €"),
mois39h: parseEuro("2 572,83 €"),
},
cdi: parseEuro("1 959,62 €"),
},
{
nom: "Technicien vidéo web",
filiere: "I",
niveau: "V",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 006,02 €"),
mois39h: parseEuro("2 292,59 €"),
},
cdi: parseEuro("1 828,83 €"),
},
];
const filieres = [
{ code: 'G', nom: 'Réalisation', icon: Clapperboard, color: 'orange' },
{ code: 'H', nom: 'Son', icon: Volume2, color: 'red' },
{ code: 'I', nom: 'Web', icon: Globe, color: 'teal' },
];
const colorClasses = {
orange: {
bg: 'bg-orange-50',
border: 'border-orange-200',
text: 'text-orange-900',
hover: 'hover:bg-orange-100',
gradient: 'from-orange-500 to-amber-600',
ring: 'ring-orange-500',
},
red: {
bg: 'bg-red-50',
border: 'border-red-200',
text: 'text-red-900',
hover: 'hover:bg-red-100',
gradient: 'from-red-500 to-rose-600',
ring: 'ring-red-500',
},
teal: {
bg: 'bg-teal-50',
border: 'border-teal-200',
text: 'text-teal-900',
hover: 'hover:bg-teal-100',
gradient: 'from-teal-500 to-cyan-600',
ring: 'ring-teal-500',
},
};
interface EmploiCardProps {
emploi: Emploi;
color: keyof typeof colorClasses;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border ${colors.border} bg-white overflow-hidden transition-all duration-200 hover:shadow-md`}>
{/* Header */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className={`w-full px-4 py-3 flex items-center justify-between ${colors.hover} transition-colors`}
>
<div className="flex items-center gap-3">
<span className={`inline-flex items-center px-2 py-1 rounded-md text-xs font-semibold ${colors.bg} ${colors.text}`}>
Niveau {emploi.niveau}
</span>
<h3 className="text-sm font-semibold text-slate-900">{emploi.nom}</h3>
</div>
{isExpanded ? (
<ChevronUp className="w-4 h-4 text-slate-400" />
) : (
<ChevronDown className="w-4 h-4 text-slate-400" />
)}
</button>
{/* Content */}
{isExpanded && (
<div className="border-t border-slate-100 p-4 space-y-4">
{/* Tabs */}
<div className="flex gap-2">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Content based on tab */}
{activeTab === 'cdi' ? (
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs text-slate-600 mb-2">Salaire mensuel minimum</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
</div>
) : (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
interface CategorieBFictionDataPart3Props {
activeFiliere: string;
}
export default function CategorieBFictionDataPart3({ activeFiliere }: CategorieBFictionDataPart3Props) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Déterminer la couleur en fonction de la filière
const getColorForFiliere = (filiereCode: string): keyof typeof colorClasses => {
const filiere = filieres.find(f => f.code === filiereCode);
return (filiere?.color as keyof typeof colorClasses) || 'blue';
};
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-orange-50 to-amber-50 p-4">
<h2 className="text-lg font-semibold text-orange-900 mb-1">
Filières G, H et I
</h2>
<p className="text-xs text-orange-600 mt-2">
{emploisData.length} emplois - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Rechercher un emploi (nom ou filière G/H/I)..."
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-cyan-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={getColorForFiliere(emploi.filiere)}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,979 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Pencil, Palette, Camera, Search, ChevronDown, ChevronUp } from 'lucide-react';
const parseEuro = (str: string) => {
if (!str || str === '-') return null;
const cleaned = str.replace(/\s/g, '').replace('€', '').replace(',', '.');
return parseFloat(cleaned);
};
const euro = (n: number | null) => {
if (n === null) return '-';
return new Intl.NumberFormat('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n) + ' €';
};
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
const emploisData: Emploi[] = [
// Filière A - Écriture
{
nom: "Chargé de recherche",
filiere: "A",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("803,07 €"),
semaine39h: parseEuro("917,80 €"),
jour7h: parseEuro("178,46 €"),
jour8h: parseEuro("203,95 €"),
mois35h: parseEuro("3 051,68 €"),
mois39h: parseEuro("3 487,62 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Chargé de sélection",
filiere: "A",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("822,67 €"),
semaine39h: parseEuro("940,19 €"),
jour7h: parseEuro("182,82 €"),
jour8h: parseEuro("208,93 €"),
mois35h: parseEuro("3 126,14 €"),
mois39h: parseEuro("3 572,72 €"),
},
cdi: parseEuro("2 590,58 €"),
},
{
nom: "Collaborateur artistique",
filiere: "A",
niveau: "IV",
cddu: {
semaine35h: parseEuro("534,27 €"),
semaine39h: parseEuro("610,60 €"),
jour7h: parseEuro("118,73 €"),
jour8h: parseEuro("135,69 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Conseiller artistique d'émission",
filiere: "A",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("807,61 €"),
semaine39h: parseEuro("922,98 €"),
jour7h: parseEuro("179,47 €"),
jour8h: parseEuro("205,11 €"),
mois35h: parseEuro("3 068,91 €"),
mois39h: parseEuro("3 507,31 €"),
},
cdi: parseEuro("2 590,58 €"),
},
{
nom: "Coordinateur d'écriture (ex script éditeur)",
filiere: "A",
niveau: "II",
cddu: {
semaine35h: parseEuro("877,44 €"),
semaine39h: parseEuro("1 002,79 €"),
jour7h: parseEuro("194,99 €"),
jour8h: parseEuro("222,84 €"),
mois35h: parseEuro("3 334,28 €"),
mois39h: parseEuro("3 810,59 €"),
},
cdi: parseEuro("3 013,00 €"),
},
{
nom: "Dir. de collection / Dir. de programmation",
filiere: "A",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 127,35 €"),
semaine39h: parseEuro("1 288,39 €"),
jour7h: parseEuro("250,52 €"),
jour8h: parseEuro("286,31 €"),
mois35h: parseEuro("4 283,91 €"),
mois39h: parseEuro("4 895,89 €"),
},
cdi: parseEuro("2 952,74 €"),
},
{
nom: "Directeur artistique",
filiere: "A",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 127,35 €"),
semaine39h: parseEuro("1 288,39 €"),
jour7h: parseEuro("250,52 €"),
jour8h: parseEuro("286,31 €"),
mois35h: parseEuro("4 283,91 €"),
mois39h: parseEuro("4 895,89 €"),
},
cdi: parseEuro("2 952,74 €"),
},
{
nom: "Directeur de la distribution",
filiere: "A",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("877,44 €"),
semaine39h: parseEuro("1 002,79 €"),
jour7h: parseEuro("194,99 €"),
jour8h: parseEuro("222,84 €"),
mois35h: parseEuro("3 334,28 €"),
mois39h: parseEuro("3 810,59 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Directeur des dialogues",
filiere: "A",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("746,08 €"),
semaine39h: parseEuro("852,66 €"),
jour7h: parseEuro("165,80 €"),
jour8h: parseEuro("189,48 €"),
mois35h: parseEuro("2 835,09 €"),
mois39h: parseEuro("3 240,10 €"),
},
cdi: parseEuro("2 048,84 €"),
},
{
nom: "Documentaliste",
filiere: "A",
niveau: "II",
cddu: {
semaine35h: parseEuro("838,98 €"),
semaine39h: parseEuro("958,83 €"),
jour7h: parseEuro("186,44 €"),
jour8h: parseEuro("213,07 €"),
mois35h: parseEuro("3 188,12 €"),
mois39h: parseEuro("3 643,55 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Enquêteur / Recherchiste",
filiere: "A",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("768,45 €"),
semaine39h: parseEuro("878,23 €"),
jour7h: parseEuro("170,77 €"),
jour8h: parseEuro("195,16 €"),
mois35h: parseEuro("2 920,11 €"),
mois39h: parseEuro("3 337,26 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Responsable d'enquête / de recherche",
filiere: "A",
niveau: "II",
cddu: {
semaine35h: parseEuro("838,98 €"),
semaine39h: parseEuro("958,83 €"),
jour7h: parseEuro("186,44 €"),
jour8h: parseEuro("213,07 €"),
mois35h: parseEuro("3 188,12 €"),
mois39h: parseEuro("3 643,55 €"),
},
cdi: parseEuro("2 892,47 €"),
},
// Filière B - Décor & Costume
{
nom: "1er assistant décorateur",
filiere: "B",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("962,49 €"),
semaine39h: parseEuro("1 099,99 €"),
jour7h: parseEuro("213,89 €"),
jour8h: parseEuro("244,44 €"),
mois35h: parseEuro("3 657,48 €"),
mois39h: parseEuro("4 179,96 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "2ème assistant décorateur",
filiere: "B",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("839,05 €"),
semaine39h: parseEuro("958,92 €"),
jour7h: parseEuro("186,46 €"),
jour8h: parseEuro("213,09 €"),
mois35h: parseEuro("3 188,41 €"),
mois39h: parseEuro("3 643,88 €"),
},
cdi: parseEuro("2 109,10 €"),
},
{
nom: "Accessoiriste",
filiere: "B",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("810,81 €"),
semaine39h: parseEuro("926,64 €"),
jour7h: parseEuro("180,18 €"),
jour8h: parseEuro("205,92 €"),
mois35h: parseEuro("3 081,06 €"),
mois39h: parseEuro("3 521,20 €"),
},
cdi: parseEuro("2 048,84 €"),
},
{
nom: "Assistant décorateur adjoint",
filiere: "B",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chef constructeur",
filiere: "B",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 221,74 €"),
semaine39h: parseEuro("1 396,28 €"),
jour7h: parseEuro("271,50 €"),
jour8h: parseEuro("310,28 €"),
mois35h: parseEuro("4 642,63 €"),
mois39h: parseEuro("5 305,84 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Chef costumier",
filiere: "B",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("894,37 €"),
semaine39h: parseEuro("1 022,14 €"),
jour7h: parseEuro("198,75 €"),
jour8h: parseEuro("227,14 €"),
mois35h: parseEuro("3 398,61 €"),
mois39h: parseEuro("3 884,11 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Chef d'équipe de décor",
filiere: "B",
niveau: "IV",
cddu: {
semaine35h: parseEuro("1 114,05 €"),
semaine39h: parseEuro("1 273,20 €"),
jour7h: parseEuro("247,57 €"),
jour8h: parseEuro("282,93 €"),
mois35h: parseEuro("4 233,38 €"),
mois39h: parseEuro("4 838,13 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Chef décorateur",
filiere: "B",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 664,48 €"),
semaine39h: parseEuro("1 902,26 €"),
jour7h: parseEuro("369,88 €"),
jour8h: parseEuro("422,73 €"),
mois35h: parseEuro("6 325,03 €"),
mois39h: parseEuro("7 228,58 €"),
},
cdi: parseEuro("3 013,00 €"),
},
{
nom: "Constructeur de décor",
filiere: "B",
niveau: "IV",
cddu: {
semaine35h: parseEuro("932,11 €"),
semaine39h: parseEuro("1 065,27 €"),
jour7h: parseEuro("207,14 €"),
jour8h: parseEuro("236,73 €"),
mois35h: parseEuro("3 542,03 €"),
mois39h: parseEuro("4 048,02 €"),
},
cdi: parseEuro("2 481,29 €"),
},
{
nom: "Costumier",
filiere: "B",
niveau: "IV",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Créateur de costume",
filiere: "B",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 640,95 €"),
semaine39h: parseEuro("1 875,37 €"),
jour7h: parseEuro("364,66 €"),
jour8h: parseEuro("416,75 €"),
mois35h: parseEuro("6 235,62 €"),
mois39h: parseEuro("7 126,40 €"),
},
cdi: parseEuro("3 013,00 €"),
},
{
nom: "Décorateur",
filiere: "B",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 069,70 €"),
semaine39h: parseEuro("1 222,51 €"),
jour7h: parseEuro("237,71 €"),
jour8h: parseEuro("271,67 €"),
mois35h: parseEuro("4 064,86 €"),
mois39h: parseEuro("4 645,54 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Dessinateur en décor",
filiere: "B",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("839,05 €"),
semaine39h: parseEuro("958,92 €"),
jour7h: parseEuro("186,46 €"),
jour8h: parseEuro("213,09 €"),
mois35h: parseEuro("3 188,41 €"),
mois39h: parseEuro("3 643,88 €"),
},
cdi: parseEuro("2 109,10 €"),
},
{
nom: "Électricien déco / Machiniste déco",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("920,25 €"),
semaine39h: parseEuro("1 051,72 €"),
jour7h: parseEuro("204,50 €"),
jour8h: parseEuro("233,72 €"),
mois35h: parseEuro("3 496,97 €"),
mois39h: parseEuro("3 996,52 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Ensemblier - décorateur",
filiere: "B",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("962,49 €"),
semaine39h: parseEuro("1 099,99 €"),
jour7h: parseEuro("213,89 €"),
jour8h: parseEuro("244,44 €"),
mois35h: parseEuro("3 657,48 €"),
mois39h: parseEuro("4 179,96 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Habilleur",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("643,70 €"),
semaine39h: parseEuro("735,66 €"),
jour7h: parseEuro("143,04 €"),
jour8h: parseEuro("163,48 €"),
mois35h: parseEuro("2 446,06 €"),
mois39h: parseEuro("2 795,49 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Maçon de décor",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("920,25 €"),
semaine39h: parseEuro("1 051,72 €"),
jour7h: parseEuro("204,50 €"),
jour8h: parseEuro("233,72 €"),
mois35h: parseEuro("3 496,97 €"),
mois39h: parseEuro("3 996,52 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Menuisier-traceur-toupilleur de décor",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("949,67 €"),
semaine39h: parseEuro("1 085,34 €"),
jour7h: parseEuro("211,04 €"),
jour8h: parseEuro("241,19 €"),
mois35h: parseEuro("3 608,76 €"),
mois39h: parseEuro("4 124,29 €"),
},
cdi: parseEuro("2 410,40 €"),
},
{
nom: "Métallier / Serrurier / Mécanicien de décor",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("920,25 €"),
semaine39h: parseEuro("1 051,72 €"),
jour7h: parseEuro("204,50 €"),
jour8h: parseEuro("233,72 €"),
mois35h: parseEuro("3 496,97 €"),
mois39h: parseEuro("3 996,52 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Peintre de décor",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("920,25 €"),
semaine39h: parseEuro("1 051,72 €"),
jour7h: parseEuro("204,50 €"),
jour8h: parseEuro("233,72 €"),
mois35h: parseEuro("3 496,97 €"),
mois39h: parseEuro("3 996,52 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Peintre en lettres / en faux bois de décor",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("920,25 €"),
semaine39h: parseEuro("1 051,72 €"),
jour7h: parseEuro("204,50 €"),
jour8h: parseEuro("233,72 €"),
mois35h: parseEuro("3 496,97 €"),
mois39h: parseEuro("3 996,52 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Régisseur d'extérieurs",
filiere: "B",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("839,05 €"),
semaine39h: parseEuro("958,92 €"),
jour7h: parseEuro("186,46 €"),
jour8h: parseEuro("213,09 €"),
mois35h: parseEuro("3 188,41 €"),
mois39h: parseEuro("3 643,88 €"),
},
cdi: parseEuro("2 109,10 €"),
},
{
nom: "Rippeur",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("822,58 €"),
semaine39h: parseEuro("940,09 €"),
jour7h: parseEuro("182,80 €"),
jour8h: parseEuro("208,91 €"),
mois35h: parseEuro("3 125,81 €"),
mois39h: parseEuro("3 572,35 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Staffeur de décor",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("949,67 €"),
semaine39h: parseEuro("1 085,34 €"),
jour7h: parseEuro("211,04 €"),
jour8h: parseEuro("241,19 €"),
mois35h: parseEuro("3 608,76 €"),
mois39h: parseEuro("4 124,29 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Styliste",
filiere: "B",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("804,93 €"),
semaine39h: parseEuro("919,92 €"),
jour7h: parseEuro("178,87 €"),
jour8h: parseEuro("204,43 €"),
mois35h: parseEuro("3 058,73 €"),
mois39h: parseEuro("3 495,68 €"),
},
cdi: parseEuro("2 048,84 €"),
},
{
nom: "Tapissier de décor",
filiere: "B",
niveau: "V",
cddu: {
semaine35h: parseEuro("920,25 €"),
semaine39h: parseEuro("1 051,72 €"),
jour7h: parseEuro("204,50 €"),
jour8h: parseEuro("233,72 €"),
mois35h: parseEuro("3 496,97 €"),
mois39h: parseEuro("3 996,52 €"),
},
cdi: parseEuro("2 169,36 €"),
},
// Filière C - Image
{
nom: "1er assistant OPV / pointeur",
filiere: "C",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("968,32 €"),
semaine39h: parseEuro("1 106,66 €"),
jour7h: parseEuro("215,18 €"),
jour8h: parseEuro("245,92 €"),
mois35h: parseEuro("3 679,63 €"),
mois39h: parseEuro("4 205,28 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "2ème assistant OPV",
filiere: "C",
niveau: "V",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,16 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant lumière",
filiere: "C",
niveau: "IV",
cddu: {
semaine35h: parseEuro("764,93 €"),
semaine39h: parseEuro("874,20 €"),
jour7h: parseEuro("169,98 €"),
jour8h: parseEuro("194,27 €"),
mois35h: parseEuro("2 906,72 €"),
mois39h: parseEuro("3 321,95 €"),
},
cdi: parseEuro("2 018,71 €"),
},
{
nom: "Assistant OPV adjoint",
filiere: "C",
niveau: "VI",
cddu: {
semaine35h: parseEuro("463,28 €"),
semaine39h: parseEuro("529,47 €"),
jour7h: parseEuro("102,95 €"),
jour8h: parseEuro("117,66 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Cadreur / OPV",
filiere: "C",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 125,04 €"),
semaine39h: parseEuro("1 285,76 €"),
jour7h: parseEuro("250,01 €"),
jour8h: parseEuro("285,72 €"),
mois35h: parseEuro("4 275,15 €"),
mois39h: parseEuro("4 885,87 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Directeur photo",
filiere: "C",
niveau: "I",
cddu: {
semaine35h: parseEuro("1 762,00 €"),
semaine39h: parseEuro("2 013,72 €"),
jour7h: parseEuro("391,56 €"),
jour8h: parseEuro("447,49 €"),
mois35h: parseEuro("6 695,61 €"),
mois39h: parseEuro("7 652,11 €"),
},
cdi: parseEuro("3 314,29 €"),
},
{
nom: "Opérateur de transfert et de traitement numérique",
filiere: "C",
niveau: "V",
cddu: {
semaine35h: parseEuro("714,32 €"),
semaine39h: parseEuro("816,36 €"),
jour7h: parseEuro("158,74 €"),
jour8h: parseEuro("181,41 €"),
mois35h: parseEuro("2 714,40 €"),
mois39h: parseEuro("3 102,17 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Opérateur spécial (steadicamer)",
filiere: "C",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 180,27 €"),
semaine39h: parseEuro("1 348,88 €"),
jour7h: parseEuro("262,28 €"),
jour8h: parseEuro("299,75 €"),
mois35h: parseEuro("4 485,04 €"),
mois39h: parseEuro("5 125,75 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Photographe de plateau",
filiere: "C",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("810,81 €"),
semaine39h: parseEuro("926,64 €"),
jour7h: parseEuro("180,18 €"),
jour8h: parseEuro("205,92 €"),
mois35h: parseEuro("3 081,06 €"),
mois39h: parseEuro("3 521,20 €"),
},
cdi: parseEuro("2 229,61 €"),
},
{
nom: "Pupitreur lumière",
filiere: "C",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("932,11 €"),
semaine39h: parseEuro("1 065,27 €"),
jour7h: parseEuro("207,14 €"),
jour8h: parseEuro("236,73 €"),
mois35h: parseEuro("3 542,03 €"),
mois39h: parseEuro("4 048,02 €"),
},
cdi: parseEuro("2 481,29 €"),
},
{
nom: "Superviseur d'effets spéciaux image",
filiere: "C",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 051,06 €"),
semaine39h: parseEuro("1 201,21 €"),
jour7h: parseEuro("233,57 €"),
jour8h: parseEuro("266,94 €"),
mois35h: parseEuro("3 994,02 €"),
mois39h: parseEuro("4 564,58 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Technicien vidéo",
filiere: "C",
niveau: "IV",
cddu: {
semaine35h: parseEuro("822,58 €"),
semaine39h: parseEuro("940,09 €"),
jour7h: parseEuro("182,80 €"),
jour8h: parseEuro("208,91 €"),
mois35h: parseEuro("3 125,81 €"),
mois39h: parseEuro("3 572,35 €"),
},
cdi: parseEuro("2 109,10 €"),
},
];
const filieres = [
{ code: 'A', nom: 'Contenu du programme et collaboration artistique', icon: Pencil, color: 'violet' },
{ code: 'B', nom: 'Costumes, décor', icon: Palette, color: 'blue' },
{ code: 'C', nom: 'Image', icon: Camera, color: 'cyan' },
];
const colorClasses = {
violet: {
bg: 'bg-violet-50',
border: 'border-violet-200',
text: 'text-violet-900',
hover: 'hover:bg-violet-100',
gradient: 'from-violet-500 to-purple-600',
ring: 'ring-violet-500',
},
blue: {
bg: 'bg-blue-50',
border: 'border-blue-200',
text: 'text-blue-900',
hover: 'hover:bg-blue-100',
gradient: 'from-blue-500 to-indigo-600',
ring: 'ring-blue-500',
},
cyan: {
bg: 'bg-cyan-50',
border: 'border-cyan-200',
text: 'text-cyan-900',
hover: 'hover:bg-cyan-100',
gradient: 'from-cyan-500 to-blue-600',
ring: 'ring-cyan-500',
},
};
interface EmploiCardProps {
emploi: Emploi;
color: keyof typeof colorClasses;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border ${colors.border} bg-white overflow-hidden transition-all duration-200 hover:shadow-md`}>
{/* Header */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className={`w-full px-4 py-3 flex items-center justify-between ${colors.hover} transition-colors`}
>
<div className="flex items-center gap-3">
<span className={`inline-flex items-center px-2 py-1 rounded-md text-xs font-semibold ${colors.bg} ${colors.text}`}>
Niveau {emploi.niveau}
</span>
<h3 className="text-sm font-semibold text-slate-900">{emploi.nom}</h3>
</div>
{isExpanded ? (
<ChevronUp className="w-4 h-4 text-slate-400" />
) : (
<ChevronDown className="w-4 h-4 text-slate-400" />
)}
</button>
{/* Content */}
{isExpanded && (
<div className="border-t border-slate-100 p-4 space-y-4">
{/* Tabs */}
<div className="flex gap-2">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Content based on tab */}
{activeTab === 'cdi' ? (
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs text-slate-600 mb-2">Salaire mensuel minimum</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
</div>
) : (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
interface CategorieBFictionContentProps {
activeFiliere: string;
}
export default function CategorieBFictionContent({ activeFiliere }: CategorieBFictionContentProps) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Obtenir les infos de la filière active
const activeFiliereInfo = filieres.find(f => f.code === activeFiliere);
const colorForActive = (activeFiliereInfo?.color as keyof typeof colorClasses) || 'violet';
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-orange-50 to-amber-50 p-4">
<h2 className="text-lg font-semibold text-orange-900 mb-1">
Filière {activeFiliere} - {activeFiliereInfo?.nom || 'Filière'}
</h2>
<p className="text-xs text-orange-600 mt-2">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={`Rechercher dans ${activeFiliereInfo?.nom || 'la filière'}...`}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-cyan-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={colorForActive}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,90 @@
"use client";
import React, { useState } from 'react';
import CategorieBFictionDataPart1 from './categorie-b-fiction-data';
import CategorieBFictionDataPart2 from './categorie-b-fiction-data-part2';
import CategorieBFictionDataPart3 from './categorie-b-fiction-data-part3';
type Filiere = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I';
const filieres: { code: Filiere; nom: string; part: 1 | 2 | 3 }[] = [
{ code: 'A', nom: 'Contenu du programme et collaboration artistique', part: 1 },
{ code: 'B', nom: 'Costumes, décor', part: 1 },
{ code: 'C', nom: 'Image', part: 1 },
{ code: 'D', nom: 'Plateaux et tournage', part: 2 },
{ code: 'E', nom: 'Postproduction', part: 2 },
{ code: 'F', nom: 'Production', part: 2 },
{ code: 'G', nom: 'Réalisation', part: 3 },
{ code: 'H', nom: 'Son', part: 3 },
{ code: 'I', nom: 'Web', part: 3 },
];
export default function CategorieBFiction() {
const [activeFiliere, setActiveFiliere] = useState<Filiere>('A');
const [hoveredFiliere, setHoveredFiliere] = useState<Filiere | null>(null);
return (
<div className="space-y-4">
{/* En-tête de la catégorie */}
<div className="rounded-xl border bg-gradient-to-br from-cyan-50 to-blue-50 p-4">
<h2 className="text-lg font-semibold text-cyan-900 mb-1">
Catégorie B - Fiction
</h2>
<p className="text-sm text-cyan-700">
Grille des salaires par filière - Production audiovisuelle fiction
</p>
{/* Ligne d'information globale supprimée pour alléger l'en-tête */}
</div>
{/* Sélection des filières */}
<div className="space-y-3">
<p className="text-xs text-slate-600">
Sélectionnez une filière pour explorer les emplois :
</p>
<div className="flex flex-wrap gap-2">
{filieres.map((filiere) => (
<div key={filiere.code} className="relative">
<button
onClick={() => setActiveFiliere(filiere.code)}
onMouseEnter={() => setHoveredFiliere(filiere.code)}
onMouseLeave={() => setHoveredFiliere(null)}
className={`relative px-4 py-2 rounded-full text-sm font-semibold transition-all ${
activeFiliere === filiere.code
? 'bg-gradient-to-r from-cyan-500 to-blue-600 text-white shadow-md'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Filière {filiere.code}
{activeFiliere === filiere.code && (
<span className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-white flex items-center justify-center">
<svg className="w-3 h-3 text-cyan-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</span>
)}
</button>
{/* Tooltip */}
{hoveredFiliere === filiere.code && (
<div className="absolute z-50 bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-1.5 bg-slate-900 text-white text-xs font-medium rounded-lg shadow-lg whitespace-nowrap animate-in fade-in slide-in-from-bottom-1 duration-200">
{filiere.nom}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px">
<div className="border-4 border-transparent border-t-slate-900" />
</div>
</div>
)}
</div>
))}
</div>
{/* Contenu */}
<div className="mt-4">
{filieres.find(f => f.code === activeFiliere)?.part === 1 && <CategorieBFictionDataPart1 activeFiliere={activeFiliere} />}
{filieres.find(f => f.code === activeFiliere)?.part === 2 && <CategorieBFictionDataPart2 activeFiliere={activeFiliere} />}
{filieres.find(f => f.code === activeFiliere)?.part === 3 && <CategorieBFictionDataPart3 activeFiliere={activeFiliere} />}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,828 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Sparkles, Film, ClipboardList, Search, ChevronDown, ChevronUp } from 'lucide-react';
const parseEuro = (str: string) => {
if (!str || str === '-') return null;
const cleaned = str.replace(/\s/g, '').replace('€', '').replace(',', '.');
return parseFloat(cleaned);
};
const euro = (n: number | null) => {
if (n === null) return '-';
return new Intl.NumberFormat('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n) + ' €';
};
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
const emploisData: Emploi[] = [
// Filière D - Maquillage & Coiffure
{
nom: "Blocker / Rigger",
filiere: "D",
niveau: "IV",
cddu: {
semaine35h: parseEuro("861,07 €"),
semaine39h: parseEuro("984,08 €"),
jour7h: parseEuro("191,35 €"),
jour8h: parseEuro("218,68 €"),
mois35h: parseEuro("3 272,07 €"),
mois39h: parseEuro("3 739,49 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chef électricien",
filiere: "D",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("975,19 €"),
semaine39h: parseEuro("1 114,51 €"),
jour7h: parseEuro("216,71 €"),
jour8h: parseEuro("247,67 €"),
mois35h: parseEuro("3 705,74 €"),
mois39h: parseEuro("4 235,12 €"),
},
cdi: parseEuro("2 289,87 €"),
},
{
nom: "Chef machiniste",
filiere: "D",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("975,19 €"),
semaine39h: parseEuro("1 114,51 €"),
jour7h: parseEuro("216,71 €"),
jour8h: parseEuro("247,67 €"),
mois35h: parseEuro("3 705,74 €"),
mois39h: parseEuro("4 235,12 €"),
},
cdi: parseEuro("2 289,87 €"),
},
{
nom: "Chef maquilleur",
filiere: "D",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("871,49 €"),
semaine39h: parseEuro("995,99 €"),
jour7h: parseEuro("193,66 €"),
jour8h: parseEuro("221,33 €"),
mois35h: parseEuro("3 311,66 €"),
mois39h: parseEuro("3 784,74 €"),
},
cdi: parseEuro("2 410,40 €"),
},
{
nom: "Coiffeur",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("703,86 €"),
semaine39h: parseEuro("804,41 €"),
jour7h: parseEuro("156,41 €"),
jour8h: parseEuro("178,76 €"),
mois35h: parseEuro("2 674,68 €"),
mois39h: parseEuro("3 056,77 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Coiffeur perruquier",
filiere: "D",
niveau: "IV",
cddu: {
semaine35h: parseEuro("880,12 €"),
semaine39h: parseEuro("1 005,85 €"),
jour7h: parseEuro("195,58 €"),
jour8h: parseEuro("223,52 €"),
mois35h: parseEuro("3 344,44 €"),
mois39h: parseEuro("3 822,21 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Électricien / Éclairagiste",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("800,11 €"),
semaine39h: parseEuro("914,41 €"),
jour7h: parseEuro("177,80 €"),
jour8h: parseEuro("203,20 €"),
mois35h: parseEuro("3 040,40 €"),
mois39h: parseEuro("3 474,73 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Machiniste",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("800,11 €"),
semaine39h: parseEuro("914,41 €"),
jour7h: parseEuro("177,80 €"),
jour8h: parseEuro("203,20 €"),
mois35h: parseEuro("3 040,40 €"),
mois39h: parseEuro("3 474,73 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Maquilleur",
filiere: "D",
niveau: "V",
cddu: {
semaine35h: parseEuro("703,86 €"),
semaine39h: parseEuro("804,41 €"),
jour7h: parseEuro("156,41 €"),
jour8h: parseEuro("178,76 €"),
mois35h: parseEuro("2 674,68 €"),
mois39h: parseEuro("3 056,77 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Maquilleur et coiffeur effets spéciaux",
filiere: "D",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("1 056,37 €"),
semaine39h: parseEuro("1 207,28 €"),
jour7h: parseEuro("234,75 €"),
jour8h: parseEuro("268,28 €"),
mois35h: parseEuro("4 014,21 €"),
mois39h: parseEuro("4 587,65 €"),
},
cdi: parseEuro("2 169,36 €"),
},
// Filière E - Post-production
{
nom: "Assistant de post-production",
filiere: "E",
niveau: "IV",
cddu: {
semaine35h: parseEuro("616,88 €"),
semaine39h: parseEuro("705,01 €"),
jour7h: parseEuro("137,09 €"),
jour8h: parseEuro("156,67 €"),
mois35h: parseEuro("2 344,16 €"),
mois39h: parseEuro("2 679,03 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant monteur",
filiere: "E",
niveau: "IV",
cddu: {
semaine35h: parseEuro("703,86 €"),
semaine39h: parseEuro("804,41 €"),
jour7h: parseEuro("156,41 €"),
jour8h: parseEuro("178,76 €"),
mois35h: parseEuro("2 674,68 €"),
mois39h: parseEuro("3 056,77 €"),
},
cdi: parseEuro("1 928,32 €"),
},
{
nom: "Assistant monteur adjoint",
filiere: "E",
niveau: "VI",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chargé de post-production",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 035,68 €"),
semaine39h: parseEuro("1 183,63 €"),
jour7h: parseEuro("230,15 €"),
jour8h: parseEuro("263,03 €"),
mois35h: parseEuro("3 935,57 €"),
mois39h: parseEuro("4 497,78 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Chef monteur",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 110,85 €"),
semaine39h: parseEuro("1 269,54 €"),
jour7h: parseEuro("246,86 €"),
jour8h: parseEuro("282,12 €"),
mois35h: parseEuro("4 221,22 €"),
mois39h: parseEuro("4 824,24 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Directeur de post-production",
filiere: "E",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 245,64 €"),
semaine39h: parseEuro("1 423,59 €"),
jour7h: parseEuro("276,81 €"),
jour8h: parseEuro("316,35 €"),
mois35h: parseEuro("4 733,44 €"),
mois39h: parseEuro("5 409,64 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Étalonneur",
filiere: "E",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("950,84 €"),
semaine39h: parseEuro("1 086,68 €"),
jour7h: parseEuro("211,30 €"),
jour8h: parseEuro("241,48 €"),
mois35h: parseEuro("3 613,20 €"),
mois39h: parseEuro("4 129,36 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Infographiste",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 035,68 €"),
semaine39h: parseEuro("1 183,63 €"),
jour7h: parseEuro("230,15 €"),
jour8h: parseEuro("263,03 €"),
mois35h: parseEuro("3 935,57 €"),
mois39h: parseEuro("4 497,78 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Mixeur",
filiere: "E",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 328,48 €"),
semaine39h: parseEuro("1 518,26 €"),
jour7h: parseEuro("295,22 €"),
jour8h: parseEuro("337,39 €"),
mois35h: parseEuro("5 048,21 €"),
mois39h: parseEuro("5 769,37 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Superviseur d'effets spéciaux postproduction",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 162,83 €"),
semaine39h: parseEuro("1 328,95 €"),
jour7h: parseEuro("258,41 €"),
jour8h: parseEuro("295,32 €"),
mois35h: parseEuro("4 418,76 €"),
mois39h: parseEuro("5 050,00 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Truquiste",
filiere: "E",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("965,63 €"),
semaine39h: parseEuro("1 103,58 €"),
jour7h: parseEuro("214,59 €"),
jour8h: parseEuro("245,24 €"),
mois35h: parseEuro("3 669,41 €"),
mois39h: parseEuro("4 193,60 €"),
},
cdi: parseEuro("2 530,92 €"),
},
// Filière F - Production
{
nom: "Administrateur de production",
filiere: "F",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("864,60 €"),
semaine39h: parseEuro("988,11 €"),
jour7h: parseEuro("192,13 €"),
jour8h: parseEuro("219,58 €"),
mois35h: parseEuro("3 285,48 €"),
mois39h: parseEuro("3 754,83 €"),
},
cdi: parseEuro("2 591,18 €"),
},
{
nom: "Aide de plateau",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("475,42 €"),
semaine39h: parseEuro("543,34 €"),
jour7h: parseEuro("105,65 €"),
jour8h: parseEuro("120,74 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant d'émission",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant de production",
filiere: "F",
niveau: "IV",
cddu: {
semaine35h: parseEuro("703,86 €"),
semaine39h: parseEuro("804,41 €"),
jour7h: parseEuro("156,41 €"),
jour8h: parseEuro("178,76 €"),
mois35h: parseEuro("2 674,68 €"),
mois39h: parseEuro("3 056,77 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Assistant de production adjoint",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Assistant régisseur adjoint",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chargé de production",
filiere: "F",
niveau: "II",
cddu: {
semaine35h: parseEuro("948,41 €"),
semaine39h: parseEuro("1 083,90 €"),
jour7h: parseEuro("210,76 €"),
jour8h: parseEuro("240,87 €"),
mois35h: parseEuro("3 603,95 €"),
mois39h: parseEuro("4 118,79 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "Chauffeur",
filiere: "F",
niveau: "VI",
cddu: {
semaine35h: parseEuro("475,42 €"),
semaine39h: parseEuro("543,34 €"),
jour7h: parseEuro("105,65 €"),
jour8h: parseEuro("120,74 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Chauffeur de salle",
filiere: "F",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("880,12 €"),
semaine39h: parseEuro("1 005,85 €"),
jour7h: parseEuro("195,58 €"),
jour8h: parseEuro("223,52 €"),
mois35h: parseEuro("3 344,44 €"),
mois39h: parseEuro("3 822,21 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Coordinateur d'émission",
filiere: "F",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("735,16 €"),
semaine39h: parseEuro("840,18 €"),
jour7h: parseEuro("163,37 €"),
jour8h: parseEuro("186,71 €"),
mois35h: parseEuro("2 793,61 €"),
mois39h: parseEuro("3 192,68 €"),
},
cdi: parseEuro("2 147,87 €"),
},
{
nom: "Directeur de production",
filiere: "F",
niveau: "I",
cddu: {
semaine35h: parseEuro("1 509,58 €"),
semaine39h: parseEuro("1 725,23 €"),
jour7h: parseEuro("335,46 €"),
jour8h: parseEuro("383,39 €"),
mois35h: parseEuro("5 736,40 €"),
mois39h: parseEuro("6 555,86 €"),
},
cdi: parseEuro("3 314,29 €"),
},
{
nom: "Producteur exécutif",
filiere: "F",
niveau: "HN",
cddu: {
semaine35h: null,
semaine39h: null,
jour7h: null,
jour8h: null,
mois35h: null,
mois39h: null,
},
cdi: null,
},
{
nom: "Régisseur / Responsable des repérages",
filiere: "F",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("818,65 €"),
semaine39h: parseEuro("935,61 €"),
jour7h: parseEuro("181,92 €"),
jour8h: parseEuro("207,91 €"),
mois35h: parseEuro("3 110,89 €"),
mois39h: parseEuro("3 555,29 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Régisseur adjoint",
filiere: "F",
niveau: "IV",
cddu: {
semaine35h: parseEuro("703,86 €"),
semaine39h: parseEuro("804,41 €"),
jour7h: parseEuro("156,41 €"),
jour8h: parseEuro("178,76 €"),
mois35h: parseEuro("2 674,68 €"),
mois39h: parseEuro("3 056,77 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Régisseur de plateau / Chef de plateau",
filiere: "F",
niveau: "IV",
cddu: {
semaine35h: parseEuro("703,86 €"),
semaine39h: parseEuro("804,41 €"),
jour7h: parseEuro("156,41 €"),
jour8h: parseEuro("178,76 €"),
mois35h: parseEuro("2 674,68 €"),
mois39h: parseEuro("3 056,77 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Régisseur général",
filiere: "F",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("948,41 €"),
semaine39h: parseEuro("1 083,90 €"),
jour7h: parseEuro("210,76 €"),
jour8h: parseEuro("240,87 €"),
mois35h: parseEuro("3 603,95 €"),
mois39h: parseEuro("4 118,79 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Responsable des enfants",
filiere: "F",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("735,16 €"),
semaine39h: parseEuro("840,18 €"),
jour7h: parseEuro("163,37 €"),
jour8h: parseEuro("186,71 €"),
mois35h: parseEuro("2 793,61 €"),
mois39h: parseEuro("3 192,68 €"),
},
cdi: parseEuro("2 048,84 €"),
},
{
nom: "Secrétaire de production",
filiere: "F",
niveau: "V",
cddu: {
semaine35h: parseEuro("616,88 €"),
semaine39h: parseEuro("705,01 €"),
jour7h: parseEuro("137,09 €"),
jour8h: parseEuro("156,67 €"),
mois35h: parseEuro("2 344,16 €"),
mois39h: parseEuro("2 679,03 €"),
},
cdi: parseEuro("1 828,83 €"),
},
];
const filieres = [
{ code: 'D', nom: 'Plateaux et tournage', icon: Sparkles, color: 'pink' },
{ code: 'E', nom: 'Postproduction', icon: Film, color: 'indigo' },
{ code: 'F', nom: 'Production', icon: ClipboardList, color: 'green' },
];
const colorClasses = {
pink: {
bg: 'bg-pink-50',
border: 'border-pink-200',
text: 'text-pink-900',
hover: 'hover:bg-pink-100',
gradient: 'from-pink-500 to-rose-600',
ring: 'ring-pink-500',
},
indigo: {
bg: 'bg-indigo-50',
border: 'border-indigo-200',
text: 'text-indigo-900',
hover: 'hover:bg-indigo-100',
gradient: 'from-indigo-500 to-purple-600',
ring: 'ring-indigo-500',
},
green: {
bg: 'bg-green-50',
border: 'border-green-200',
text: 'text-green-900',
hover: 'hover:bg-green-100',
gradient: 'from-green-500 to-emerald-600',
ring: 'ring-green-500',
},
};
interface EmploiCardProps {
emploi: Emploi;
color: keyof typeof colorClasses;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border ${colors.border} bg-white overflow-hidden transition-all duration-200 hover:shadow-md`}>
{/* Header */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className={`w-full px-4 py-3 flex items-center justify-between ${colors.hover} transition-colors`}
>
<div className="flex items-center gap-3">
<span className={`inline-flex items-center px-2 py-1 rounded-md text-xs font-semibold ${colors.bg} ${colors.text}`}>
Niveau {emploi.niveau}
</span>
<h3 className="text-sm font-semibold text-slate-900">{emploi.nom}</h3>
</div>
{isExpanded ? (
<ChevronUp className="w-4 h-4 text-slate-400" />
) : (
<ChevronDown className="w-4 h-4 text-slate-400" />
)}
</button>
{/* Content */}
{isExpanded && (
<div className="border-t border-slate-100 p-4 space-y-4">
{/* Tabs */}
<div className="flex gap-2">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Content based on tab */}
{activeTab === 'cdi' ? (
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs text-slate-600 mb-2">Salaire mensuel minimum</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
</div>
) : (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
interface CategorieBFluxDataPart2Props {
activeFiliere: string;
}
export default function CategorieBFluxDataPart2({ activeFiliere }: CategorieBFluxDataPart2Props) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Déterminer la couleur en fonction de la filière
const getColorForFiliere = (filiereCode: string): keyof typeof colorClasses => {
const filiere = filieres.find(f => f.code === filiereCode);
return (filiere?.color as keyof typeof colorClasses) || 'blue';
};
const activeFiliereInfo = filieres.find(f => f.code === activeFiliere);
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-amber-50 to-orange-50 p-4">
<h2 className="text-lg font-semibold text-amber-900 mb-1">
Filière {activeFiliere} - {activeFiliereInfo?.nom || 'Filière'}
</h2>
<p className="text-xs text-amber-600 mt-2">
{emploisData.length} emplois - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={`Rechercher dans ${activeFiliereInfo?.nom || 'la filière'}...`}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-amber-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={getColorForFiliere(emploi.filiere)}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,632 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Clapperboard, Volume2, Globe, Search, ChevronDown, ChevronUp } from 'lucide-react';
const parseEuro = (str: string) => {
if (!str || str === '-') return null;
const cleaned = str.replace(/\s/g, '').replace('€', '').replace(',', '.');
return parseFloat(cleaned);
};
const euro = (n: number | null) => {
if (n === null) return '-';
return new Intl.NumberFormat('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n) + ' €';
};
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
const emploisData: Emploi[] = [
// Filière G - Réalisation
{
nom: "1er assistant réalisateur",
filiere: "G",
niveau: "II",
cddu: {
semaine35h: parseEuro("948,41 €"),
semaine39h: parseEuro("1 083,90 €"),
jour7h: parseEuro("210,76 €"),
jour8h: parseEuro("240,87 €"),
mois35h: parseEuro("3 603,95 €"),
mois39h: parseEuro("4 118,79 €"),
},
cdi: parseEuro("2 892,47 €"),
},
{
nom: "2ème assistant réalisateur",
filiere: "G",
niveau: "IV",
cddu: {
semaine35h: parseEuro("793,15 €"),
semaine39h: parseEuro("906,46 €"),
jour7h: parseEuro("176,26 €"),
jour8h: parseEuro("201,43 €"),
mois35h: parseEuro("3 013,97 €"),
mois39h: parseEuro("3 444,52 €"),
},
cdi: parseEuro("1 988,58 €"),
},
{
nom: "Assistant réalisateur",
filiere: "G",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("881,28 €"),
semaine39h: parseEuro("1 007,18 €"),
jour7h: parseEuro("195,84 €"),
jour8h: parseEuro("223,82 €"),
mois35h: parseEuro("3 348,87 €"),
mois39h: parseEuro("3 827,27 €"),
},
cdi: parseEuro("2 229,61 €"),
},
{
nom: "Conseiller technique à la réalisation",
filiere: "G",
niveau: "II",
cddu: {
semaine35h: parseEuro("1 035,68 €"),
semaine39h: parseEuro("1 183,63 €"),
jour7h: parseEuro("230,15 €"),
jour8h: parseEuro("263,03 €"),
mois35h: parseEuro("3 935,57 €"),
mois39h: parseEuro("4 497,78 €"),
},
cdi: parseEuro("3 013,00 €"),
},
{
nom: "Réalisateur",
filiere: "G",
niveau: "HN",
cddu: {
semaine35h: null,
semaine39h: null,
jour7h: null,
jour8h: null,
mois35h: null,
mois39h: null,
},
cdi: null,
},
{
nom: "Scripte",
filiere: "G",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("948,41 €"),
semaine39h: parseEuro("1 083,90 €"),
jour7h: parseEuro("210,76 €"),
jour8h: parseEuro("240,87 €"),
mois35h: parseEuro("3 603,95 €"),
mois39h: parseEuro("4 118,79 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Storyboarder",
filiere: "G",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("881,28 €"),
semaine39h: parseEuro("1 007,18 €"),
jour7h: parseEuro("195,84 €"),
jour8h: parseEuro("223,82 €"),
mois35h: parseEuro("3 348,87 €"),
mois39h: parseEuro("3 827,27 €"),
},
cdi: parseEuro("2 169,36 €"),
},
// Filière H - Son
{
nom: "Assistant son",
filiere: "H",
niveau: "IV",
cddu: {
semaine35h: parseEuro("703,86 €"),
semaine39h: parseEuro("804,41 €"),
jour7h: parseEuro("156,41 €"),
jour8h: parseEuro("178,76 €"),
mois35h: parseEuro("2 674,68 €"),
mois39h: parseEuro("3 056,77 €"),
},
cdi: parseEuro("1 928,32 €"),
},
{
nom: "Assistant son adjoint",
filiere: "H",
niveau: "VI",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("2 342,34 €"),
mois39h: parseEuro("2 676,95 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Bruiteur",
filiere: "H",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 035,68 €"),
semaine39h: parseEuro("1 183,63 €"),
jour7h: parseEuro("230,15 €"),
jour8h: parseEuro("263,03 €"),
mois35h: parseEuro("3 935,57 €"),
mois39h: parseEuro("4 497,78 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "Chef OPS / Ingénieur du son",
filiere: "H",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 194,86 €"),
semaine39h: parseEuro("1 365,55 €"),
jour7h: parseEuro("265,52 €"),
jour8h: parseEuro("303,46 €"),
mois35h: parseEuro("4 540,45 €"),
mois39h: parseEuro("5 189,07 €"),
},
cdi: parseEuro("2 651,44 €"),
},
{
nom: "Mixeur (direct ou conditions du direct)",
filiere: "H",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("1 035,68 €"),
semaine39h: parseEuro("1 183,63 €"),
jour7h: parseEuro("230,15 €"),
jour8h: parseEuro("263,03 €"),
mois35h: parseEuro("3 935,57 €"),
mois39h: parseEuro("4 497,78 €"),
},
cdi: parseEuro("2 530,92 €"),
},
{
nom: "OPS",
filiere: "H",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("881,28 €"),
semaine39h: parseEuro("1 007,18 €"),
jour7h: parseEuro("195,84 €"),
jour8h: parseEuro("223,82 €"),
mois35h: parseEuro("3 348,87 €"),
mois39h: parseEuro("3 827,27 €"),
},
cdi: parseEuro("2 169,36 €"),
},
{
nom: "Perchiste / 1er assistant son",
filiere: "H",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("869,19 €"),
semaine39h: parseEuro("993,36 €"),
jour7h: parseEuro("193,15 €"),
jour8h: parseEuro("220,75 €"),
mois35h: parseEuro("3 302,93 €"),
mois39h: parseEuro("3 774,77 €"),
},
cdi: parseEuro("2 410,40 €"),
},
{
nom: "Technicien instruments (backliner)",
filiere: "H",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("881,28 €"),
semaine39h: parseEuro("1 007,18 €"),
jour7h: parseEuro("195,84 €"),
jour8h: parseEuro("223,82 €"),
mois35h: parseEuro("3 348,87 €"),
mois39h: parseEuro("3 827,27 €"),
},
cdi: parseEuro("2 386,53 €"),
},
// Filière I - Web
{
nom: "Assistant technique web",
filiere: "I",
niveau: "VI",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("1 976,67 €"),
mois39h: parseEuro("2 259,04 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Concepteur de programme web",
filiere: "I",
niveau: "I",
cddu: {
semaine35h: parseEuro("774,28 €"),
semaine39h: parseEuro("884,89 €"),
jour7h: parseEuro("172,06 €"),
jour8h: parseEuro("196,64 €"),
mois35h: parseEuro("3 352,63 €"),
mois39h: parseEuro("3 831,57 €"),
},
cdi: parseEuro("2 969,12 €"),
},
{
nom: "Coordinateur de diffusion web",
filiere: "I",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("512,31 €"),
semaine39h: parseEuro("585,49 €"),
jour7h: parseEuro("113,85 €"),
jour8h: parseEuro("130,11 €"),
mois35h: parseEuro("2 218,29 €"),
mois39h: parseEuro("2 535,18 €"),
},
cdi: parseEuro("1 959,62 €"),
},
{
nom: "Coordinateur de production web",
filiere: "I",
niveau: "II",
cddu: {
semaine35h: parseEuro("582,17 €"),
semaine39h: parseEuro("665,34 €"),
jour7h: parseEuro("129,37 €"),
jour8h: parseEuro("147,85 €"),
mois35h: parseEuro("2 520,81 €"),
mois39h: parseEuro("2 880,91 €"),
},
cdi: parseEuro("2 197,15 €"),
},
{
nom: "Designer web",
filiere: "I",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("535,60 €"),
semaine39h: parseEuro("612,11 €"),
jour7h: parseEuro("119,02 €"),
jour8h: parseEuro("136,03 €"),
mois35h: parseEuro("2 319,14 €"),
mois39h: parseEuro("2 650,44 €"),
},
cdi: parseEuro("2 019,01 €"),
},
{
nom: "Editeur artistique web",
filiere: "I",
niveau: "IV",
cddu: {
semaine35h: parseEuro("494,84 €"),
semaine39h: parseEuro("565,53 €"),
jour7h: parseEuro("109,96 €"),
jour8h: parseEuro("125,67 €"),
mois35h: parseEuro("2 142,66 €"),
mois39h: parseEuro("2 448,75 €"),
},
cdi: parseEuro("1 900,24 €"),
},
{
nom: "Gestionnaire de diffusion internet (traffic manager)",
filiere: "I",
niveau: "V",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("1 976,67 €"),
mois39h: parseEuro("2 259,04 €"),
},
cdi: parseEuro("1 828,83 €"),
},
{
nom: "Opérateur web / Opérateur multicam web",
filiere: "I",
niveau: "IIIA",
cddu: {
semaine35h: parseEuro("558,88 €"),
semaine39h: parseEuro("638,72 €"),
jour7h: parseEuro("124,20 €"),
jour8h: parseEuro("141,94 €"),
mois35h: parseEuro("2 419,95 €"),
mois39h: parseEuro("2 765,65 €"),
},
cdi: parseEuro("2 137,77 €"),
},
{
nom: "Technicien de développement web",
filiere: "I",
niveau: "IIIB",
cddu: {
semaine35h: parseEuro("512,31 €"),
semaine39h: parseEuro("585,49 €"),
jour7h: parseEuro("113,85 €"),
jour8h: parseEuro("130,11 €"),
mois35h: parseEuro("2 218,29 €"),
mois39h: parseEuro("2 535,18 €"),
},
cdi: parseEuro("1 959,62 €"),
},
{
nom: "Technicien vidéo web",
filiere: "I",
niveau: "V",
cddu: {
semaine35h: parseEuro("456,50 €"),
semaine39h: parseEuro("521,72 €"),
jour7h: parseEuro("101,45 €"),
jour8h: parseEuro("115,94 €"),
mois35h: parseEuro("1 976,67 €"),
mois39h: parseEuro("2 259,04 €"),
},
cdi: parseEuro("1 828,83 €"),
},
];
const filieres = [
{ code: 'G', nom: 'Réalisation', icon: Clapperboard, color: 'orange' },
{ code: 'H', nom: 'Son', icon: Volume2, color: 'red' },
{ code: 'I', nom: 'Web', icon: Globe, color: 'teal' },
];
const colorClasses = {
orange: {
bg: 'bg-orange-50',
border: 'border-orange-200',
text: 'text-orange-900',
hover: 'hover:bg-orange-100',
gradient: 'from-orange-500 to-amber-600',
ring: 'ring-orange-500',
},
red: {
bg: 'bg-red-50',
border: 'border-red-200',
text: 'text-red-900',
hover: 'hover:bg-red-100',
gradient: 'from-red-500 to-rose-600',
ring: 'ring-red-500',
},
teal: {
bg: 'bg-teal-50',
border: 'border-teal-200',
text: 'text-teal-900',
hover: 'hover:bg-teal-100',
gradient: 'from-teal-500 to-cyan-600',
ring: 'ring-teal-500',
},
};
interface EmploiCardProps {
emploi: Emploi;
color: keyof typeof colorClasses;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border ${colors.border} bg-white overflow-hidden transition-all duration-200 hover:shadow-md`}>
{/* Header */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className={`w-full px-4 py-3 flex items-center justify-between ${colors.hover} transition-colors`}
>
<div className="flex items-center gap-3">
<span className={`inline-flex items-center px-2 py-1 rounded-md text-xs font-semibold ${colors.bg} ${colors.text}`}>
Niveau {emploi.niveau}
</span>
<h3 className="text-sm font-semibold text-slate-900">{emploi.nom}</h3>
</div>
{isExpanded ? (
<ChevronUp className="w-4 h-4 text-slate-400" />
) : (
<ChevronDown className="w-4 h-4 text-slate-400" />
)}
</button>
{/* Content */}
{isExpanded && (
<div className="border-t border-slate-100 p-4 space-y-4">
{/* Tabs */}
<div className="flex gap-2">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-xs font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-slate-50 text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Content based on tab */}
{activeTab === 'cdi' ? (
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs text-slate-600 mb-2">Salaire mensuel minimum</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
</div>
) : (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
interface CategorieBFluxDataPart3Props {
activeFiliere: string;
}
export default function CategorieBFluxDataPart3({ activeFiliere }: CategorieBFluxDataPart3Props) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Déterminer la couleur en fonction de la filière
const getColorForFiliere = (filiereCode: string): keyof typeof colorClasses => {
const filiere = filieres.find(f => f.code === filiereCode);
return (filiere?.color as keyof typeof colorClasses) || 'blue';
};
const activeFiliereInfo = filieres.find(f => f.code === activeFiliere);
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-amber-50 to-orange-50 p-4">
<h2 className="text-lg font-semibold text-amber-900 mb-1">
Filière {activeFiliere} - {activeFiliereInfo?.nom || 'Filière'}
</h2>
<p className="text-xs text-amber-600 mt-2">
{emploisData.length} emplois - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={`Rechercher dans ${activeFiliereInfo?.nom || 'la filière'}...`}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-amber-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={getColorForFiliere(emploi.filiere)}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,92 @@
"use client";
import React, { useState } from 'react';
import CategorieBFluxDataPart1 from './categorie-b-flux-data';
import CategorieBFluxDataPart2 from './categorie-b-flux-data-part2';
import CategorieBFluxDataPart3 from './categorie-b-flux-data-part3';
type Filiere = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I';
const filieres: { code: Filiere; nom: string; part: 1 | 2 | 3 }[] = [
{ code: 'A', nom: 'Contenu du programme et collaboration artistique', part: 1 },
{ code: 'B', nom: 'Costumes, décor', part: 1 },
{ code: 'C', nom: 'Image', part: 1 },
{ code: 'D', nom: 'Plateaux et tournage', part: 2 },
{ code: 'E', nom: 'Postproduction', part: 2 },
{ code: 'F', nom: 'Production', part: 2 },
{ code: 'G', nom: 'Réalisation', part: 3 },
{ code: 'H', nom: 'Son', part: 3 },
{ code: 'I', nom: 'Web', part: 3 },
];
export default function CategorieBFlux() {
const [activeFiliere, setActiveFiliere] = useState<Filiere>('A');
const [hoveredFiliere, setHoveredFiliere] = useState<Filiere | null>(null);
return (
<div className="space-y-4">
{/* En-tête de la catégorie */}
<div className="rounded-xl border bg-gradient-to-br from-amber-50 to-orange-50 p-4">
<h2 className="text-lg font-semibold text-amber-900 mb-1">
Catégorie B - Flux
</h2>
<p className="text-sm text-amber-700">
Grille des salaires par filière - Programmes de flux (jeux, divertissement, plateaux)
</p>
{/* Ligne d'information globale supprimée pour alléger l'en-tête */}
</div>
{/* Boutons de filières */}
<div className="space-y-3">
<p className="text-xs text-slate-600">
Sélectionnez une filière pour explorer les emplois :
</p>
<div className="flex flex-wrap gap-2">
{filieres.map((filiere) => (
<div key={filiere.code} className="relative">
<button
onClick={() => setActiveFiliere(filiere.code)}
onMouseEnter={() => setHoveredFiliere(filiere.code)}
onMouseLeave={() => setHoveredFiliere(null)}
className={`relative px-4 py-2 rounded-full text-sm font-semibold transition-all ${
activeFiliere === filiere.code
? 'bg-gradient-to-r from-amber-500 to-orange-600 text-white shadow-md'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Filière {filiere.code}
{activeFiliere === filiere.code && (
<span className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-white flex items-center justify-center">
<svg className="w-3 h-3 text-amber-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</span>
)}
</button>
{hoveredFiliere === filiere.code && (
<div className="absolute z-50 bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-1.5 bg-slate-900 text-white text-xs font-medium rounded-lg shadow-lg whitespace-nowrap animate-in fade-in slide-in-from-bottom-1 duration-200">
{filiere.nom}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px">
<div className="border-4 border-transparent border-t-slate-900" />
</div>
</div>
)}
</div>
))}
</div>
{/* Contenu */}
<div className="mt-4">
{(() => {
const activePart = filieres.find(f => f.code === activeFiliere)?.part;
if (activePart === 1) return <CategorieBFluxDataPart1 key={activeFiliere} activeFiliere={activeFiliere} />;
if (activePart === 2) return <CategorieBFluxDataPart2 key={activeFiliere} activeFiliere={activeFiliere} />;
if (activePart === 3) return <CategorieBFluxDataPart3 key={activeFiliere} activeFiliere={activeFiliere} />;
return null;
})()}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,352 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Sparkles, Film, ClipboardList, Search, ChevronDown, ChevronUp } from 'lucide-react';
// Fonction pour parser les montants en euros
function parseEuro(value: string | null): number | null {
if (!value || value === '-') return null;
const cleaned = value.replace(/\s/g, '').replace('€', '').replace(',', '.');
const parsed = parseFloat(cleaned);
return isNaN(parsed) ? null : parsed;
}
// Fonction pour formater en euros
function euro(value: number | null): string {
if (value === null) return '-';
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR'
}).format(value);
}
// Interface pour un emploi
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
// Données des emplois - Filières D, E, F
const emploisData: Emploi[] = [
// Filière D - Maquillage & Coiffure
{ nom: "Blocker / Rigger", filiere: "D", niveau: "IV", cddu: { semaine35h: 852.54, semaine39h: 974.34, jour7h: 189.45, jour8h: 216.52, mois35h: 3239.67, mois39h: 3702.47 }, cdi: 1828.83 },
{ nom: "Chef électricien", filiere: "D", niveau: "IIIB", cddu: { semaine35h: 965.54, semaine39h: 1103.47, jour7h: 214.56, jour8h: 245.22, mois35h: 3669.05, mois39h: 4193.19 }, cdi: 2289.87 },
{ nom: "Chef machiniste", filiere: "D", niveau: "IIIB", cddu: { semaine35h: 965.54, semaine39h: 1103.47, jour7h: 214.56, jour8h: 245.22, mois35h: 3669.05, mois39h: 4193.19 }, cdi: 2289.87 },
{ nom: "Chef maquilleur", filiere: "D", niveau: "IIIA", cddu: { semaine35h: 862.86, semaine39h: 986.13, jour7h: 191.75, jour8h: 219.14, mois35h: 3278.87, mois39h: 3747.27 }, cdi: 2410.40 },
{ nom: "Coiffeur", filiere: "D", niveau: "V", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1828.83 },
{ nom: "Coiffeur perruquier", filiere: "D", niveau: "IV", cddu: { semaine35h: 871.40, semaine39h: 995.89, jour7h: 193.65, jour8h: 221.31, mois35h: 3311.33, mois39h: 3784.37 }, cdi: 1988.58 },
{ nom: "Conducteur de groupe", filiere: "D", niveau: "IV", cddu: { semaine35h: 859.91, semaine39h: 982.76, jour7h: 191.09, jour8h: 218.39, mois35h: 3267.67, mois39h: 3734.47 }, cdi: 1988.58 },
{ nom: "Électricien / Éclairagiste", filiere: "D", niveau: "V", cddu: { semaine35h: 792.18, semaine39h: 905.35, jour7h: 176.04, jour8h: 201.19, mois35h: 3010.30, mois39h: 3440.33 }, cdi: 1828.83 },
{ nom: "Machiniste", filiere: "D", niveau: "V", cddu: { semaine35h: 792.18, semaine39h: 905.35, jour7h: 176.04, jour8h: 201.19, mois35h: 3010.30, mois39h: 3440.33 }, cdi: 1828.83 },
{ nom: "Maquilleur", filiere: "D", niveau: "V", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1828.83 },
{ nom: "Maquilleur et coiffeur effets spéciaux", filiere: "D", niveau: "IIIB", cddu: { semaine35h: 1045.91, semaine39h: 1195.33, jour7h: 232.42, jour8h: 265.63, mois35h: 3974.47, mois39h: 4542.23 }, cdi: 2169.36 },
{ nom: "Prothésiste", filiere: "D", niveau: "IIIB", cddu: { semaine35h: 1045.91, semaine39h: 1195.33, jour7h: 232.42, jour8h: 265.63, mois35h: 3974.47, mois39h: 4542.23 }, cdi: 2169.36 },
// Filière E - Post-production
{ nom: "Assistant de post-production", filiere: "E", niveau: "IV", cddu: { semaine35h: 610.78, semaine39h: 698.03, jour7h: 135.73, jour8h: 155.12, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Assistant monteur", filiere: "E", niveau: "IV", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1928.32 },
{ nom: "Assistant monteur adjoint", filiere: "E", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Chargé de post-production", filiere: "E", niveau: "IIIA", cddu: { semaine35h: 1025.42, semaine39h: 1171.91, jour7h: 227.87, jour8h: 260.42, mois35h: 3896.61, mois39h: 4453.25 }, cdi: 2651.44 },
{ nom: "Chef monteur", filiere: "E", niveau: "IIIA", cddu: { semaine35h: 1099.85, semaine39h: 1256.97, jour7h: 244.41, jour8h: 279.33, mois35h: 4179.43, mois39h: 4776.47 }, cdi: 2651.44 },
{ nom: "Conformateur", filiere: "E", niveau: "IIIB", cddu: { semaine35h: 941.43, semaine39h: 1075.92, jour7h: 209.21, jour8h: 239.09, mois35h: 3577.43, mois39h: 4088.48 }, cdi: 2169.36 },
{ nom: "Directeur de post-production", filiere: "E", niveau: "II", cddu: { semaine35h: 1245.64, semaine39h: 1423.59, jour7h: 276.81, jour8h: 316.35, mois35h: 4733.44, mois39h: 5409.64 }, cdi: 2892.47 },
{ nom: "Étalonneur", filiere: "E", niveau: "IIIB", cddu: { semaine35h: 941.43, semaine39h: 1075.92, jour7h: 209.21, jour8h: 239.09, mois35h: 3577.43, mois39h: 4088.48 }, cdi: 2169.36 },
{ nom: "Infographiste", filiere: "E", niveau: "IIIA", cddu: { semaine35h: 1025.42, semaine39h: 1171.91, jour7h: 227.87, jour8h: 260.42, mois35h: 3896.61, mois39h: 4453.25 }, cdi: 2530.92 },
{ nom: "Mixeur", filiere: "E", niveau: "II", cddu: { semaine35h: 1328.48, semaine39h: 1518.26, jour7h: 295.22, jour8h: 337.39, mois35h: 5048.21, mois39h: 5769.37 }, cdi: 2892.47 },
{ nom: "Mixeur (direct ou conditions du direct)", filiere: "E", niveau: "IIIA", cddu: { semaine35h: 1025.42, semaine39h: 1171.91, jour7h: 227.87, jour8h: 260.42, mois35h: 3896.61, mois39h: 4453.25 }, cdi: 2530.92 },
{ nom: "Superviseur d'effets spéciaux postproduction", filiere: "E", niveau: "IIIA", cddu: { semaine35h: 1162.83, semaine39h: 1328.95, jour7h: 258.41, jour8h: 295.32, mois35h: 4418.76, mois39h: 5050.00 }, cdi: 2651.44 },
{ nom: "Truquiste", filiere: "E", niveau: "IIIA", cddu: { semaine35h: 956.07, semaine39h: 1092.65, jour7h: 212.46, jour8h: 242.81, mois35h: 3633.08, mois39h: 4152.08 }, cdi: 2530.92 },
// Filière F - Production
{ nom: "Administrateur de production", filiere: "F", niveau: "IIIA", cddu: { semaine35h: 856.04, semaine39h: 978.33, jour7h: 190.23, jour8h: 217.41, mois35h: 3252.95, mois39h: 3717.65 }, cdi: 2591.18 },
{ nom: "Aide de plateau", filiere: "F", niveau: "VI", cddu: { semaine35h: 470.71, semaine39h: 537.96, jour7h: 104.60, jour8h: 119.55, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Assistant d'émission", filiere: "F", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Assistant de production", filiere: "F", niveau: "IV", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1988.58 },
{ nom: "Assistant de production adjoint", filiere: "F", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Assistant régisseur adjoint", filiere: "F", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Chargé de production", filiere: "F", niveau: "II", cddu: { semaine35h: 939.02, semaine39h: 1073.16, jour7h: 208.67, jour8h: 238.48, mois35h: 3568.27, mois39h: 4078.01 }, cdi: 2892.47 },
{ nom: "Chauffeur", filiere: "F", niveau: "VI", cddu: { semaine35h: 470.71, semaine39h: 537.96, jour7h: 104.60, jour8h: 119.55, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Chauffeur de salle", filiere: "F", niveau: "IIIB", cddu: { semaine35h: 871.40, semaine39h: 995.89, jour7h: 193.65, jour8h: 221.31, mois35h: 3311.33, mois39h: 3784.37 }, cdi: 2169.36 },
{ nom: "Comptable de production", filiere: "F", niveau: "IV", cddu: { semaine35h: 732.47, semaine39h: 837.11, jour7h: 162.77, jour8h: 186.02, mois35h: 2783.39, mois39h: 3181.01 }, cdi: 2169.36 },
{ nom: "Coordinateur d'émission", filiere: "F", niveau: "IIIB", cddu: { semaine35h: 727.88, semaine39h: 831.86, jour7h: 161.75, jour8h: 184.86, mois35h: 2765.95, mois39h: 3161.07 }, cdi: 2147.87 },
{ nom: "Directeur de production", filiere: "F", niveau: "I", cddu: { semaine35h: 1509.58, semaine39h: 1725.23, jour7h: 335.46, jour8h: 383.39, mois35h: 5736.40, mois39h: 6555.86 }, cdi: 3314.29 },
{ nom: "Dresseur", filiere: "F", niveau: "II", cddu: { semaine35h: 1099.85, semaine39h: 1256.97, jour7h: 244.41, jour8h: 279.33, mois35h: 4179.43, mois39h: 4776.47 }, cdi: 2952.74 },
{ nom: "Producteur exécutif", filiere: "", niveau: "HN", cddu: { semaine35h: null, semaine39h: null, jour7h: null, jour8h: null, mois35h: null, mois39h: null }, cdi: null },
{ nom: "Régisseur / Responsable des repérages", filiere: "F", niveau: "IIIB", cddu: { semaine35h: 810.55, semaine39h: 926.34, jour7h: 180.12, jour8h: 205.85, mois35h: 3080.09, mois39h: 3520.09 }, cdi: 2169.36 },
{ nom: "Régisseur adjoint", filiere: "F", niveau: "IV", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1988.58 },
{ nom: "Régisseur de plateau / Chef de plateau", filiere: "F", niveau: "IV", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1828.83 },
{ nom: "Régisseur général", filiere: "F", niveau: "IIIA", cddu: { semaine35h: 939.02, semaine39h: 1073.16, jour7h: 208.67, jour8h: 238.48, mois35h: 3568.27, mois39h: 4078.01 }, cdi: 2651.44 },
{ nom: "Régulateur de stationnement", filiere: "F", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Responsable des enfants", filiere: "F", niveau: "IIIB", cddu: { semaine35h: 727.88, semaine39h: 831.86, jour7h: 161.75, jour8h: 184.86, mois35h: 2765.95, mois39h: 3161.07 }, cdi: 2048.84 },
{ nom: "Secrétaire de production", filiere: "F", niveau: "V", cddu: { semaine35h: 610.78, semaine39h: 698.03, jour7h: 135.73, jour8h: 155.12, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
];
// Définition des filières
const filieres = [
{ code: 'D', nom: 'Plateaux et tournage', icon: Sparkles, color: 'pink' },
{ code: 'E', nom: 'Postproduction', icon: Film, color: 'indigo' },
{ code: 'F', nom: 'Production', icon: ClipboardList, color: 'green' },
];
// Classes de couleurs pour chaque filière
const colorClasses: Record<string, {
bg: string;
border: string;
text: string;
hover: string;
gradient: string;
ring: string;
}> = {
pink: {
bg: 'from-pink-50 to-rose-50',
border: 'border-pink-200',
text: 'text-pink-700',
hover: 'hover:border-pink-300',
gradient: 'from-pink-500 to-rose-600',
ring: 'ring-pink-200',
},
indigo: {
bg: 'from-indigo-50 to-purple-50',
border: 'border-indigo-200',
text: 'text-indigo-700',
hover: 'hover:border-indigo-300',
gradient: 'from-indigo-500 to-purple-600',
ring: 'ring-indigo-200',
},
green: {
bg: 'from-green-50 to-emerald-50',
border: 'border-green-200',
text: 'text-green-700',
hover: 'hover:border-green-300',
gradient: 'from-green-500 to-emerald-600',
ring: 'ring-green-200',
},
};
// Composant pour une carte d'emploi
interface EmploiCardProps {
emploi: Emploi;
color: string;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border-2 ${colors.border} bg-white overflow-hidden transition-all ${colors.hover}`}>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="w-full p-4 text-left flex items-center justify-between hover:bg-slate-50 transition-colors"
>
<div className="flex-1">
<h4 className="font-semibold text-slate-900">{emploi.nom}</h4>
<p className="text-xs text-slate-500 mt-0.5">
{emploi.niveau !== 'HN' ? (
<>
Niveau {emploi.niveau} CDI mensuel : <span className="font-semibold">{euro(emploi.cdi)}</span>
</>
) : (
<>Niveau Hors Niveau <span className="font-semibold">Rémunération non définie par la CCN</span></>
)}
</p>
</div>
{isExpanded ? (
<ChevronUp className="w-5 h-5 text-slate-400 flex-shrink-0" />
) : (
<ChevronDown className="w-5 h-5 text-slate-400 flex-shrink-0" />
)}
</button>
{isExpanded && emploi.niveau !== 'HN' && (
<div className="border-t p-4 bg-slate-50">
{/* Tabs CDI / CDDU */}
<div className="flex gap-2 mb-4">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-white text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-white text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Contenu CDI */}
{activeTab === 'cdi' && (
<div className={`rounded-lg bg-gradient-to-br ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Salaire mensuel</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
<p className="text-xs text-slate-500 mt-1">Base 35h / mois</p>
</div>
)}
{/* Contenu CDDU */}
{activeTab === 'cddu' && (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg bg-gradient-to-br ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
// Composant principal
interface CategorieBHorsDataPart2Props {
activeFiliere: string;
}
export default function CategorieBHorsDataPart2({ activeFiliere }: CategorieBHorsDataPart2Props) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Déterminer la couleur en fonction de la filière
const getColorForFiliere = (filiereCode: string): keyof typeof colorClasses => {
const filiere = filieres.find(f => f.code === filiereCode);
return (filiere?.color as keyof typeof colorClasses) || 'blue';
};
const activeFiliereInfo = filieres.find(f => f.code === activeFiliere);
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-emerald-50 to-teal-50 p-4">
<h2 className="text-lg font-semibold text-emerald-900 mb-1">
Filière {activeFiliere} - {activeFiliereInfo?.nom || 'Filière'}
</h2>
<p className="text-xs text-emerald-600 mt-2">
{emploisData.length} emplois - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={`Rechercher dans ${activeFiliereInfo?.nom || 'la filière'}...`}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={getColorForFiliere(emploi.filiere)}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,334 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Clapperboard, Volume2, Globe, Search, ChevronDown, ChevronUp } from 'lucide-react';
// Fonction pour parser les montants en euros
function parseEuro(value: string | null): number | null {
if (!value || value === '-') return null;
const cleaned = value.replace(/\s/g, '').replace('€', '').replace(',', '.');
const parsed = parseFloat(cleaned);
return isNaN(parsed) ? null : parsed;
}
// Fonction pour formater en euros
function euro(value: number | null): string {
if (value === null) return '-';
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR'
}).format(value);
}
// Interface pour un emploi
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
// Données des emplois - Filières G, H, I
const emploisData: Emploi[] = [
// Filière G - Réalisation
{ nom: "1er assistant réalisateur", filiere: "G", niveau: "II", cddu: { semaine35h: 939.02, semaine39h: 1073.16, jour7h: 208.67, jour8h: 238.48, mois35h: 3568.27, mois39h: 4078.01 }, cdi: 2892.47 },
{ nom: "2ème assistant réalisateur", filiere: "G", niveau: "IV", cddu: { semaine35h: 785.30, semaine39h: 897.48, jour7h: 174.51, jour8h: 199.44, mois35h: 2984.13, mois39h: 3410.42 }, cdi: 1988.58 },
{ nom: "Assistant réalisateur", filiere: "G", niveau: "IIIB", cddu: { semaine35h: 872.56, semaine39h: 997.21, jour7h: 193.90, jour8h: 221.60, mois35h: 3315.71, mois39h: 3789.38 }, cdi: 2229.61 },
{ nom: "Assistant réalisateur adjoint", filiere: "G", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Assistant scripte adjoint", filiere: "G", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Conseiller technique à la réalisation", filiere: "G", niveau: "II", cddu: { semaine35h: 1025.42, semaine39h: 1171.91, jour7h: 227.87, jour8h: 260.42, mois35h: 3896.61, mois39h: 4453.25 }, cdi: 3013.00 },
{ nom: "Réalisateur", filiere: "", niveau: "HN", cddu: { semaine35h: null, semaine39h: null, jour7h: null, jour8h: null, mois35h: null, mois39h: null }, cdi: null },
{ nom: "Répétiteur", filiere: "G", niveau: "IIIB", cddu: { semaine35h: 727.88, semaine39h: 831.86, jour7h: 161.75, jour8h: 184.86, mois35h: 2765.95, mois39h: 3161.07 }, cdi: 2048.84 },
{ nom: "Scripte", filiere: "G", niveau: "IIIA", cddu: { semaine35h: 939.02, semaine39h: 1073.16, jour7h: 208.67, jour8h: 238.48, mois35h: 3568.27, mois39h: 4078.01 }, cdi: 2651.44 },
{ nom: "Storyboarder", filiere: "G", niveau: "IIIB", cddu: { semaine35h: 872.56, semaine39h: 997.21, jour7h: 193.90, jour8h: 221.60, mois35h: 3315.71, mois39h: 3789.38 }, cdi: 2169.36 },
// Filière H - Son
{ nom: "Assistant son", filiere: "H", niveau: "IV", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1928.32 },
{ nom: "Assistant son adjoint", filiere: "H", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Bruiteur", filiere: "H", niveau: "IIIA", cddu: { semaine35h: 1025.42, semaine39h: 1171.91, jour7h: 227.87, jour8h: 260.42, mois35h: 3896.61, mois39h: 4453.25 }, cdi: 2530.92 },
{ nom: "Chef OPS / Ingénieur du son", filiere: "H", niveau: "IIIA", cddu: { semaine35h: 1194.86, semaine39h: 1365.55, jour7h: 265.52, jour8h: 303.46, mois35h: 4540.45, mois39h: 5189.07 }, cdi: 2651.44 },
{ nom: "Mixeur (direct ou conditions du direct)", filiere: "H", niveau: "IIIA", cddu: { semaine35h: 1025.42, semaine39h: 1171.91, jour7h: 227.87, jour8h: 260.42, mois35h: 3896.61, mois39h: 4453.25 }, cdi: 2530.92 },
{ nom: "OPS", filiere: "H", niveau: "IIIB", cddu: { semaine35h: 872.56, semaine39h: 997.21, jour7h: 193.90, jour8h: 221.60, mois35h: 3315.71, mois39h: 3789.38 }, cdi: 2169.36 },
{ nom: "Perchiste / 1er assistant son", filiere: "H", niveau: "IIIA", cddu: { semaine35h: 860.59, semaine39h: 983.53, jour7h: 191.24, jour8h: 218.56, mois35h: 3270.23, mois39h: 3737.39 }, cdi: 2410.40 },
{ nom: "Technicien instruments (backliner)", filiere: "H", niveau: "IIIB", cddu: { semaine35h: 872.56, semaine39h: 997.21, jour7h: 193.90, jour8h: 221.60, mois35h: 3315.71, mois39h: 3789.38 }, cdi: 2386.53 },
// Filière I - Web
{ nom: "Assistant technique web", filiere: "I", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 1957.10, mois39h: 2236.67 }, cdi: 1828.83 },
{ nom: "Concepteur de programme web", filiere: "I", niveau: "I", cddu: { semaine35h: 766.61, semaine39h: 876.13, jour7h: 170.36, jour8h: 194.70, mois35h: 3319.44, mois39h: 3793.63 }, cdi: 2969.12 },
{ nom: "Coordinateur de diffusion web", filiere: "I", niveau: "IIIB", cddu: { semaine35h: 507.23, semaine39h: 579.70, jour7h: 112.72, jour8h: 128.82, mois35h: 2196.32, mois39h: 2510.08 }, cdi: 1959.62 },
{ nom: "Coordinateur de production web", filiere: "I", niveau: "II", cddu: { semaine35h: 576.41, semaine39h: 658.75, jour7h: 128.09, jour8h: 146.39, mois35h: 2495.85, mois39h: 2852.39 }, cdi: 2197.15 },
{ nom: "Designer web", filiere: "I", niveau: "IIIA", cddu: { semaine35h: 530.30, semaine39h: 606.05, jour7h: 117.84, jour8h: 134.68, mois35h: 2296.18, mois39h: 2624.20 }, cdi: 2019.01 },
{ nom: "Editeur artistique web", filiere: "I", niveau: "IV", cddu: { semaine35h: 489.94, semaine39h: 559.93, jour7h: 108.88, jour8h: 124.43, mois35h: 2121.44, mois39h: 2424.50 }, cdi: 1900.24 },
{ nom: "Gestionnaire de diffusion internet (traffic manager)", filiere: "I", niveau: "V", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 1957.10, mois39h: 2236.67 }, cdi: 1828.83 },
{ nom: "Opérateur web / Opérateur multicam web", filiere: "I", niveau: "IIIA", cddu: { semaine35h: 553.35, semaine39h: 632.40, jour7h: 122.97, jour8h: 140.53, mois35h: 2395.99, mois39h: 2738.27 }, cdi: 2137.77 },
{ nom: "Technicien de développement web", filiere: "I", niveau: "IIIB", cddu: { semaine35h: 507.23, semaine39h: 579.70, jour7h: 112.72, jour8h: 128.82, mois35h: 2196.32, mois39h: 2510.08 }, cdi: 1959.62 },
{ nom: "Technicien vidéo web", filiere: "I", niveau: "V", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 1957.10, mois39h: 2236.67 }, cdi: 1828.83 },
];
// Définition des filières
const filieres = [
{ code: 'G', nom: 'Réalisation', icon: Clapperboard, color: 'orange' },
{ code: 'H', nom: 'Son', icon: Volume2, color: 'red' },
{ code: 'I', nom: 'Web', icon: Globe, color: 'teal' },
];
// Classes de couleurs pour chaque filière
const colorClasses: Record<string, {
bg: string;
border: string;
text: string;
hover: string;
gradient: string;
ring: string;
}> = {
orange: {
bg: 'from-orange-50 to-amber-50',
border: 'border-orange-200',
text: 'text-orange-700',
hover: 'hover:border-orange-300',
gradient: 'from-orange-500 to-amber-600',
ring: 'ring-orange-200',
},
red: {
bg: 'from-red-50 to-rose-50',
border: 'border-red-200',
text: 'text-red-700',
hover: 'hover:border-red-300',
gradient: 'from-red-500 to-rose-600',
ring: 'ring-red-200',
},
teal: {
bg: 'from-teal-50 to-cyan-50',
border: 'border-teal-200',
text: 'text-teal-700',
hover: 'hover:border-teal-300',
gradient: 'from-teal-500 to-cyan-600',
ring: 'ring-teal-200',
},
};
// Composant pour une carte d'emploi
interface EmploiCardProps {
emploi: Emploi;
color: string;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border-2 ${colors.border} bg-white overflow-hidden transition-all ${colors.hover}`}>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="w-full p-4 text-left flex items-center justify-between hover:bg-slate-50 transition-colors"
>
<div className="flex-1">
<h4 className="font-semibold text-slate-900">{emploi.nom}</h4>
<p className="text-xs text-slate-500 mt-0.5">
{emploi.niveau !== 'HN' ? (
<>
Niveau {emploi.niveau} CDI mensuel : <span className="font-semibold">{euro(emploi.cdi)}</span>
</>
) : (
<>Niveau Hors Niveau <span className="font-semibold">Rémunération non définie par la CCN</span></>
)}
</p>
</div>
{isExpanded ? (
<ChevronUp className="w-5 h-5 text-slate-400 flex-shrink-0" />
) : (
<ChevronDown className="w-5 h-5 text-slate-400 flex-shrink-0" />
)}
</button>
{isExpanded && emploi.niveau !== 'HN' && (
<div className="border-t p-4 bg-slate-50">
{/* Tabs CDI / CDDU */}
<div className="flex gap-2 mb-4">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-white text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-white text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Contenu CDI */}
{activeTab === 'cdi' && (
<div className={`rounded-lg bg-gradient-to-br ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Salaire mensuel</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
<p className="text-xs text-slate-500 mt-1">Base 35h / mois</p>
</div>
)}
{/* Contenu CDDU */}
{activeTab === 'cddu' && (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg bg-gradient-to-br ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
// Composant principal
interface CategorieBHorsDataPart3Props {
activeFiliere: string;
}
export default function CategorieBHorsDataPart3({ activeFiliere }: CategorieBHorsDataPart3Props) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Déterminer la couleur en fonction de la filière
const getColorForFiliere = (filiereCode: string): keyof typeof colorClasses => {
const filiere = filieres.find(f => f.code === filiereCode);
return (filiere?.color as keyof typeof colorClasses) || 'blue';
};
const activeFiliereInfo = filieres.find(f => f.code === activeFiliere);
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-emerald-50 to-teal-50 p-4">
<h2 className="text-lg font-semibold text-emerald-900 mb-1">
Filière {activeFiliere} - {activeFiliereInfo?.nom || 'Filière'}
</h2>
<p className="text-xs text-emerald-600 mt-2">
{emploisData.length} emplois - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={`Rechercher dans ${activeFiliereInfo?.nom || 'la filière'}...`}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={getColorForFiliere(emploi.filiere)}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,367 @@
"use client";
import React, { useState, useMemo } from 'react';
import { Pencil, Palette, Camera, Search, ChevronDown, ChevronUp } from 'lucide-react';
// Fonction pour parser les montants en euros
function parseEuro(value: string | null): number | null {
if (!value || value === '-') return null;
const cleaned = value.replace(/\s/g, '').replace('€', '').replace(',', '.');
const parsed = parseFloat(cleaned);
return isNaN(parsed) ? null : parsed;
}
// Fonction pour formater en euros
function euro(value: number | null): string {
if (value === null) return '-';
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR'
}).format(value);
}
// Interface pour un emploi
interface Emploi {
nom: string;
filiere: string;
niveau: string;
cddu: {
semaine35h: number | null;
semaine39h: number | null;
jour7h: number | null;
jour8h: number | null;
mois35h: number | null;
mois39h: number | null;
};
cdi: number | null;
}
// Données des emplois - Filières A, B, C
const emploisData: Emploi[] = [
// Filière A - Écriture
{ nom: "Animatronicien", filiere: "A", niveau: "IIIA", cddu: { semaine35h: 769.64, semaine39h: 879.58, jour7h: 171.03, jour8h: 195.46, mois35h: 2924.61, mois39h: 3342.41 }, cdi: 2530.92 },
{ nom: "Chargé d'enquête / de recherche", filiere: "A", niveau: "IIIA", cddu: { semaine35h: 783.49, semaine39h: 895.41, jour7h: 174.11, jour8h: 198.98, mois35h: 2977.24, mois39h: 3402.56 }, cdi: 2530.92 },
{ nom: "Chargé de sélection", filiere: "A", niveau: "IIIA", cddu: { semaine35h: 802.60, semaine39h: 917.26, jour7h: 178.36, jour8h: 203.84, mois35h: 3049.89, mois39h: 3485.58 }, cdi: 2590.58 },
{ nom: "Collaborateur artistique", filiere: "A", niveau: "IV", cddu: { semaine35h: 521.24, semaine39h: 595.71, jour7h: 115.83, jour8h: 132.38, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Collaborateur de sélection", filiere: "A", niveau: "IV", cddu: { semaine35h: 628.00, semaine39h: 717.71, jour7h: 139.56, jour8h: 159.49, mois35h: 2386.40, mois39h: 2727.30 }, cdi: 2048.84 },
{ nom: "Conseiller artistique d'émission", filiere: "A", niveau: "IIIA", cddu: { semaine35h: 787.91, semaine39h: 900.47, jour7h: 175.09, jour8h: 200.10, mois35h: 2994.06, mois39h: 3421.77 }, cdi: 2590.58 },
{ nom: "Coordinateur d'écriture (ex script éditeur)", filiere: "A", niveau: "II", cddu: { semaine35h: 856.04, semaine39h: 978.33, jour7h: 190.23, jour8h: 217.41, mois35h: 3252.95, mois39h: 3717.65 }, cdi: 3013.00 },
{ nom: "Dir. de collection / Dir. de programmation", filiere: "A", niveau: "II", cddu: { semaine35h: 1099.85, semaine39h: 1256.97, jour7h: 244.41, jour8h: 279.33, mois35h: 4179.43, mois39h: 4776.47 }, cdi: 2952.74 },
{ nom: "Directeur artistique", filiere: "A", niveau: "II", cddu: { semaine35h: 1099.85, semaine39h: 1256.97, jour7h: 244.41, jour8h: 279.33, mois35h: 4179.43, mois39h: 4776.47 }, cdi: 2952.74 },
{ nom: "Directeur de jeux", filiere: "A", niveau: "I", cddu: { semaine35h: 1099.85, semaine39h: 1256.97, jour7h: 244.41, jour8h: 279.33, mois35h: 4179.43, mois39h: 4776.47 }, cdi: 2952.14 },
{ nom: "Directeur de la distribution", filiere: "A", niveau: "IIIA", cddu: { semaine35h: 856.04, semaine39h: 978.33, jour7h: 190.23, jour8h: 217.41, mois35h: 3252.95, mois39h: 3717.65 }, cdi: 2530.92 },
{ nom: "Directeur de sélection", filiere: "A", niveau: "I", cddu: { semaine35h: 1099.85, semaine39h: 1256.97, jour7h: 244.41, jour8h: 279.33, mois35h: 4179.43, mois39h: 4776.47 }, cdi: 2892.47 },
{ nom: "Directeur des dialogues", filiere: "A", niveau: "IIIB", cddu: { semaine35h: 727.88, semaine39h: 831.86, jour7h: 161.75, jour8h: 184.86, mois35h: 2765.95, mois39h: 3161.07 }, cdi: 2048.84 },
{ nom: "Documentaliste", filiere: "A", niveau: "II", cddu: { semaine35h: 818.52, semaine39h: 935.45, jour7h: 181.89, jour8h: 207.88, mois35h: 3110.36, mois39h: 3554.68 }, cdi: 2892.47 },
{ nom: "Enquêteur / Recherchiste", filiere: "A", niveau: "IIIB", cddu: { semaine35h: 749.71, semaine39h: 856.81, jour7h: 166.60, jour8h: 190.40, mois35h: 2848.89, mois39h: 3255.86 }, cdi: 2169.36 },
{ nom: "Illustrateur sonore", filiere: "A", niveau: "IIIA", cddu: { semaine35h: 769.64, semaine39h: 879.58, jour7h: 171.03, jour8h: 195.46, mois35h: 2924.61, mois39h: 3342.41 }, cdi: 2410.40 },
{ nom: "Préparateur de questions", filiere: "A", niveau: "IV", cddu: { semaine35h: 628.00, semaine39h: 717.71, jour7h: 139.56, jour8h: 159.49, mois35h: 2386.40, mois39h: 2727.30 }, cdi: 2147.87 },
{ nom: "Producteur artistique", filiere: "A", niveau: "II", cddu: { semaine35h: 1099.85, semaine39h: 1256.97, jour7h: 244.41, jour8h: 279.33, mois35h: 4179.43, mois39h: 4776.47 }, cdi: 2952.74 },
{ nom: "Programmateur artistique d'émission", filiere: "A", niveau: "IIIB", cddu: { semaine35h: 727.88, semaine39h: 831.86, jour7h: 161.75, jour8h: 184.86, mois35h: 2765.95, mois39h: 3161.07 }, cdi: 2048.84 },
{ nom: "Responsable d'enquête / de recherche", filiere: "A", niveau: "II", cddu: { semaine35h: 818.52, semaine39h: 935.45, jour7h: 181.89, jour8h: 207.88, mois35h: 3110.36, mois39h: 3554.68 }, cdi: 2892.47 },
{ nom: "Responsable de questions", filiere: "A", niveau: "IIIA", cddu: { semaine35h: 769.64, semaine39h: 879.58, jour7h: 171.03, jour8h: 195.46, mois35h: 2924.61, mois39h: 3342.41 }, cdi: 2651.44 },
// Filière B - Décor & Costume
{ nom: "1er assistant décorateur", filiere: "B", niveau: "IIIA", cddu: { semaine35h: 939.02, semaine39h: 1073.16, jour7h: 208.67, jour8h: 238.48, mois35h: 3568.27, mois39h: 4078.01 }, cdi: 2530.92 },
{ nom: "2ème assistant décorateur", filiere: "B", niveau: "IIIB", cddu: { semaine35h: 818.59, semaine39h: 935.53, jour7h: 181.91, jour8h: 207.90, mois35h: 3110.64, mois39h: 3555.01 }, cdi: 2109.10 },
{ nom: "Accessoiriste", filiere: "B", niveau: "IIIB", cddu: { semaine35h: 791.03, semaine39h: 904.03, jour7h: 175.78, jour8h: 200.90, mois35h: 3005.91, mois39h: 3435.32 }, cdi: 2048.84 },
{ nom: "Assistant décorateur adjoint", filiere: "B", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Chef constructeur", filiere: "B", niveau: "IIIA", cddu: { semaine35h: 1203.69, semaine39h: 1375.64, jour7h: 267.49, jour8h: 305.70, mois35h: 4574.02, mois39h: 5227.43 }, cdi: 2651.44 },
{ nom: "Chef costumier", filiere: "B", niveau: "IIIA", cddu: { semaine35h: 872.56, semaine39h: 997.21, jour7h: 193.90, jour8h: 221.60, mois35h: 3315.71, mois39h: 3789.38 }, cdi: 2169.36 },
{ nom: "Chef d'équipe de décor", filiere: "B", niveau: "IV", cddu: { semaine35h: 1086.87, semaine39h: 1242.14, jour7h: 241.53, jour8h: 276.03, mois35h: 4130.12, mois39h: 4720.13 }, cdi: 2530.92 },
{ nom: "Chef décorateur", filiere: "B", niveau: "II", cddu: { semaine35h: 1639.88, semaine39h: 1874.15, jour7h: 364.42, jour8h: 416.48, mois35h: 6231.55, mois39h: 7121.75 }, cdi: 3013.00 },
{ nom: "Constructeur de décor", filiere: "B", niveau: "IV", cddu: { semaine35h: 909.38, semaine39h: 1039.29, jour7h: 202.08, jour8h: 230.95, mois35h: 3455.63, mois39h: 3949.29 }, cdi: 2481.29 },
{ nom: "Costumier", filiere: "B", niveau: "IV", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1828.83 },
{ nom: "Créateur de costume", filiere: "B", niveau: "II", cddu: { semaine35h: 1616.70, semaine39h: 1847.66, jour7h: 359.27, jour8h: 410.59, mois35h: 6143.47, mois39h: 7021.08 }, cdi: 3013.00 },
{ nom: "Décorateur", filiere: "B", niveau: "II", cddu: { semaine35h: 1043.61, semaine39h: 1192.70, jour7h: 231.91, jour8h: 265.04, mois35h: 3965.71, mois39h: 4532.23 }, cdi: 2892.47 },
{ nom: "Dessinateur en décor", filiere: "B", niveau: "IIIB", cddu: { semaine35h: 818.59, semaine39h: 935.53, jour7h: 181.91, jour8h: 207.90, mois35h: 3110.64, mois39h: 3555.01 }, cdi: 2109.10 },
{ nom: "Électricien déco / Machiniste déco", filiere: "B", niveau: "V", cddu: { semaine35h: 897.81, semaine39h: 1026.07, jour7h: 199.51, jour8h: 228.02, mois35h: 3411.68, mois39h: 3899.05 }, cdi: 2169.36 },
{ nom: "Ensemblier - décorateur", filiere: "B", niveau: "IIIA", cddu: { semaine35h: 939.02, semaine39h: 1073.16, jour7h: 208.67, jour8h: 238.48, mois35h: 3568.27, mois39h: 4078.01 }, cdi: 2530.92 },
{ nom: "Habilleur", filiere: "B", niveau: "V", cddu: { semaine35h: 628.00, semaine39h: 717.71, jour7h: 139.56, jour8h: 159.49, mois35h: 2386.40, mois39h: 2727.30 }, cdi: 1828.83 },
{ nom: "Maçon de décor", filiere: "B", niveau: "V", cddu: { semaine35h: 897.81, semaine39h: 1026.07, jour7h: 199.51, jour8h: 228.02, mois35h: 3411.68, mois39h: 3899.05 }, cdi: 2169.36 },
{ nom: "Menuisier-traceur-toupilleur de décor", filiere: "B", niveau: "V", cddu: { semaine35h: 926.51, semaine39h: 1058.87, jour7h: 205.89, jour8h: 235.30, mois35h: 3520.74, mois39h: 4023.70 }, cdi: 2410.40 },
{ nom: "Métallier / Serrurier / Mécanicien de décor", filiere: "B", niveau: "V", cddu: { semaine35h: 897.81, semaine39h: 1026.07, jour7h: 199.51, jour8h: 228.02, mois35h: 3411.68, mois39h: 3899.05 }, cdi: 2169.36 },
{ nom: "Peintre de décor", filiere: "B", niveau: "V", cddu: { semaine35h: 897.81, semaine39h: 1026.07, jour7h: 199.51, jour8h: 228.02, mois35h: 3411.68, mois39h: 3899.05 }, cdi: 2169.36 },
{ nom: "Peintre en lettres / en faux bois de décor", filiere: "B", niveau: "V", cddu: { semaine35h: 897.81, semaine39h: 1026.07, jour7h: 199.51, jour8h: 228.02, mois35h: 3411.68, mois39h: 3899.05 }, cdi: 2169.36 },
{ nom: "Régisseur d'extérieurs", filiere: "B", niveau: "IIIB", cddu: { semaine35h: 818.59, semaine39h: 935.53, jour7h: 181.91, jour8h: 207.90, mois35h: 3110.64, mois39h: 3555.01 }, cdi: 2109.10 },
{ nom: "Rippeur", filiere: "B", niveau: "V", cddu: { semaine35h: 802.52, semaine39h: 917.17, jour7h: 178.34, jour8h: 203.81, mois35h: 3049.58, mois39h: 3485.22 }, cdi: 1828.83 },
{ nom: "Staffeur de décor", filiere: "B", niveau: "V", cddu: { semaine35h: 926.51, semaine39h: 1058.87, jour7h: 205.89, jour8h: 235.30, mois35h: 3520.74, mois39h: 4023.70 }, cdi: 2169.36 },
{ nom: "Styliste", filiere: "B", niveau: "IIIB", cddu: { semaine35h: 785.30, semaine39h: 897.48, jour7h: 174.51, jour8h: 199.44, mois35h: 2984.13, mois39h: 3410.42 }, cdi: 2048.84 },
{ nom: "Tapissier de décor", filiere: "B", niveau: "V", cddu: { semaine35h: 897.81, semaine39h: 1026.07, jour7h: 199.51, jour8h: 228.02, mois35h: 3411.68, mois39h: 3899.05 }, cdi: 2169.36 },
// Filière C - Image
{ nom: "1er assistant OPV / pointeur", filiere: "C", niveau: "IIIA", cddu: { semaine35h: 944.71, semaine39h: 1079.66, jour7h: 209.93, jour8h: 239.93, mois35h: 3589.89, mois39h: 4102.71 }, cdi: 2651.44 },
{ nom: "2ème assistant OPV", filiere: "C", niveau: "V", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.50 }, cdi: 1828.83 },
{ nom: "Assistant lumière", filiere: "C", niveau: "IV", cddu: { semaine35h: 746.27, semaine39h: 852.88, jour7h: 165.84, jour8h: 189.53, mois35h: 2835.82, mois39h: 3240.93 }, cdi: 2018.71 },
{ nom: "Assistant OPV adjoint", filiere: "C", niveau: "VI", cddu: { semaine35h: 451.99, semaine39h: 516.55, jour7h: 100.44, jour8h: 114.79, mois35h: 2342.34, mois39h: 2676.95 }, cdi: 1828.83 },
{ nom: "Cadreur / OPV", filiere: "C", niveau: "IIIA", cddu: { semaine35h: 1097.60, semaine39h: 1254.40, jour7h: 243.91, jour8h: 278.76, mois35h: 4170.87, mois39h: 4766.70 }, cdi: 2651.44 },
{ nom: "Chef OPV", filiere: "C", niveau: "II", cddu: { semaine35h: 1245.64, semaine39h: 1423.59, jour7h: 276.81, jour8h: 316.35, mois35h: 4733.44, mois39h: 5409.64 }, cdi: 3013.00 },
{ nom: "Directeur photo", filiere: "C", niveau: "I", cddu: { semaine35h: 1735.96, semaine39h: 1983.96, jour7h: 385.77, jour8h: 440.88, mois35h: 6596.66, mois39h: 7539.02 }, cdi: 3314.29 },
{ nom: "Ingénieur de la vision", filiere: "C", niveau: "II", cddu: { semaine35h: 1245.64, semaine39h: 1423.59, jour7h: 276.81, jour8h: 316.35, mois35h: 4733.44, mois39h: 5409.64 }, cdi: 3013.00 },
{ nom: "Ingénieur de la vision adjoint", filiere: "C", niveau: "IIIB", cddu: { semaine35h: 872.56, semaine39h: 997.21, jour7h: 193.90, jour8h: 221.60, mois35h: 3315.71, mois39h: 3789.38 }, cdi: 2169.36 },
{ nom: "Monteur", filiere: "C", niveau: "IIIB", cddu: { semaine35h: 918.47, semaine39h: 1049.68, jour7h: 204.10, jour8h: 233.26, mois35h: 3490.19, mois39h: 3988.78 }, cdi: 2289.87 },
{ nom: "Opérateur de transfert et de traitement numérique", filiere: "C", niveau: "V", cddu: { semaine35h: 696.89, semaine39h: 796.45, jour7h: 154.87, jour8h: 176.99, mois35h: 2648.20, mois39h: 3026.51 }, cdi: 1828.83 },
{ nom: "Opérateur magnéto / Opérateur magnéto ralenti", filiere: "C", niveau: "V", cddu: { semaine35h: 662.45, semaine39h: 757.08, jour7h: 147.21, jour8h: 168.24, mois35h: 2517.30, mois39h: 2876.90 }, cdi: 1828.83 },
{ nom: "Opérateur régie-vidéo", filiere: "C", niveau: "V", cddu: { semaine35h: 662.45, semaine39h: 757.08, jour7h: 147.21, jour8h: 168.24, mois35h: 2517.30, mois39h: 2876.90 }, cdi: 1828.83 },
{ nom: "Opérateur spécial (steadicamer)", filiere: "C", niveau: "IIIA", cddu: { semaine35h: 1162.83, semaine39h: 1328.95, jour7h: 258.41, jour8h: 295.32, mois35h: 4418.76, mois39h: 5050.00 }, cdi: 2651.44 },
{ nom: "Opérateur synthétiseur", filiere: "C", niveau: "V", cddu: { semaine35h: 662.45, semaine39h: 757.08, jour7h: 147.21, jour8h: 168.24, mois35h: 2517.30, mois39h: 2876.90 }, cdi: 1828.83 },
{ nom: "Photographe de plateau", filiere: "C", niveau: "IIIB", cddu: { semaine35h: 791.03, semaine39h: 904.03, jour7h: 175.78, jour8h: 200.90, mois35h: 3005.91, mois39h: 3435.32 }, cdi: 2229.61 },
{ nom: "Pupitreur lumière", filiere: "C", niveau: "IIIB", cddu: { semaine35h: 909.38, semaine39h: 1039.29, jour7h: 202.08, jour8h: 230.95, mois35h: 3455.63, mois39h: 3949.29 }, cdi: 2481.29 },
{ nom: "Superviseur d'effets spéciaux image", filiere: "C", niveau: "IIIA", cddu: { semaine35h: 1025.42, semaine39h: 1171.91, jour7h: 227.87, jour8h: 260.42, mois35h: 3896.61, mois39h: 4453.25 }, cdi: 2530.92 },
{ nom: "Technicien truquiste", filiere: "C", niveau: "IV", cddu: { semaine35h: 802.52, semaine39h: 917.17, jour7h: 178.34, jour8h: 203.81, mois35h: 3049.58, mois39h: 3485.22 }, cdi: 2109.10 },
{ nom: "Technicien vidéo", filiere: "C", niveau: "IV", cddu: { semaine35h: 802.52, semaine39h: 917.17, jour7h: 178.34, jour8h: 203.81, mois35h: 3049.58, mois39h: 3485.22 }, cdi: 2109.10 },
];
// Définition des filières
const filieres = [
{ code: 'A', nom: 'Contenu du programme et collaboration artistique', icon: Pencil, color: 'violet' },
{ code: 'B', nom: 'Costumes, décor', icon: Palette, color: 'blue' },
{ code: 'C', nom: 'Image', icon: Camera, color: 'cyan' },
];
// Classes de couleurs pour chaque filière
const colorClasses: Record<string, {
bg: string;
border: string;
text: string;
hover: string;
gradient: string;
ring: string;
}> = {
violet: {
bg: 'from-violet-50 to-purple-50',
border: 'border-violet-200',
text: 'text-violet-700',
hover: 'hover:border-violet-300',
gradient: 'from-violet-500 to-purple-600',
ring: 'ring-violet-200',
},
blue: {
bg: 'from-blue-50 to-indigo-50',
border: 'border-blue-200',
text: 'text-blue-700',
hover: 'hover:border-blue-300',
gradient: 'from-blue-500 to-indigo-600',
ring: 'ring-blue-200',
},
cyan: {
bg: 'from-cyan-50 to-sky-50',
border: 'border-cyan-200',
text: 'text-cyan-700',
hover: 'hover:border-cyan-300',
gradient: 'from-cyan-500 to-sky-600',
ring: 'ring-cyan-200',
},
};
// Composant pour une carte d'emploi
interface EmploiCardProps {
emploi: Emploi;
color: string;
}
function EmploiCard({ emploi, color }: EmploiCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [activeTab, setActiveTab] = useState<'cddu' | 'cdi'>('cdi');
const colors = colorClasses[color];
return (
<div className={`rounded-xl border-2 ${colors.border} bg-white overflow-hidden transition-all ${colors.hover}`}>
<button
onClick={() => setIsExpanded(!isExpanded)}
className="w-full p-4 text-left flex items-center justify-between hover:bg-slate-50 transition-colors"
>
<div className="flex-1">
<h4 className="font-semibold text-slate-900">{emploi.nom}</h4>
<p className="text-xs text-slate-500 mt-0.5">
Niveau {emploi.niveau} CDI mensuel : <span className="font-semibold">{euro(emploi.cdi)}</span>
</p>
</div>
{isExpanded ? (
<ChevronUp className="w-5 h-5 text-slate-400 flex-shrink-0" />
) : (
<ChevronDown className="w-5 h-5 text-slate-400 flex-shrink-0" />
)}
</button>
{isExpanded && (
<div className="border-t p-4 bg-slate-50">
{/* Tabs CDI / CDDU */}
<div className="flex gap-2 mb-4">
<button
onClick={() => setActiveTab('cdi')}
className={`flex-1 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'cdi'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-white text-slate-600 hover:bg-slate-100'
}`}
>
CDI / CDD
</button>
<button
onClick={() => setActiveTab('cddu')}
className={`flex-1 px-4 py-2 rounded-lg text-sm font-semibold transition-all ${
activeTab === 'cddu'
? `bg-gradient-to-r ${colors.gradient} text-white shadow-sm`
: 'bg-white text-slate-600 hover:bg-slate-100'
}`}
>
CDDU
</button>
</div>
{/* Contenu CDI */}
{activeTab === 'cdi' && (
<div className={`rounded-lg bg-gradient-to-br ${colors.bg} border ${colors.border} p-4`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Salaire mensuel</p>
<p className={`text-2xl font-bold ${colors.text}`}>{euro(emploi.cdi)}</p>
<p className="text-xs text-slate-500 mt-1">Base 35h / mois</p>
</div>
)}
{/* Contenu CDDU */}
{activeTab === 'cddu' && (
<div className="space-y-3">
{/* Par semaine */}
<div className={`rounded-lg bg-gradient-to-br ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par semaine</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.semaine39h)}</p>
</div>
</div>
</div>
{/* Par jour */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par jour</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 7h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour7h)}</p>
</div>
<div>
<p className="text-slate-500">Base 8h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.jour8h)}</p>
</div>
</div>
</div>
{/* Par mois */}
<div className={`rounded-lg ${colors.bg} border ${colors.border} p-3`}>
<p className="text-xs font-semibold text-slate-700 mb-2">Par mois</p>
<div className="grid grid-cols-2 gap-3 text-xs">
<div>
<p className="text-slate-500">Base 35h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois35h)}</p>
</div>
<div>
<p className="text-slate-500">Base 39h</p>
<p className={`font-bold ${colors.text}`}>{euro(emploi.cddu.mois39h)}</p>
</div>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
// Composant principal
interface CategorieBHorsDataProps {
activeFiliere: string;
}
export default function CategorieBHorsData({ activeFiliere }: CategorieBHorsDataProps) {
const [searchTerm, setSearchTerm] = useState('');
const filteredEmplois = useMemo(() => {
// D'abord filtrer par la filière active
let result = emploisData.filter(e => e.filiere === activeFiliere);
// Puis appliquer le filtre de recherche
if (searchTerm) {
result = result.filter(e =>
e.nom.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return result.sort((a, b) => a.nom.localeCompare(b.nom));
}, [activeFiliere, searchTerm]);
// Déterminer la couleur en fonction de la filière
const getColorForFiliere = (filiereCode: string): keyof typeof colorClasses => {
const filiere = filieres.find(f => f.code === filiereCode);
return (filiere?.color as keyof typeof colorClasses) || 'blue';
};
const activeFiliereInfo = filieres.find(f => f.code === activeFiliere);
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-xl border bg-gradient-to-br from-emerald-50 to-teal-50 p-4">
<h2 className="text-lg font-semibold text-emerald-900 mb-1">
Filière {activeFiliere} - {activeFiliereInfo?.nom || 'Filière'}
</h2>
<p className="text-xs text-emerald-600 mt-2">
{emploisData.length} emplois - Grille de salaires CCNPA (IDCC 2642) - Valeurs 2025
</p>
</div>
{/* Recherche */}
<div className="space-y-4">
{/* Barre de recherche */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder={`Rechercher dans ${activeFiliereInfo?.nom || 'la filière'}...`}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-slate-200 focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-sm"
/>
</div>
{/* Résultats */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-700">
{filteredEmplois.length} emploi{filteredEmplois.length > 1 ? 's' : ''} trouvé{filteredEmplois.length > 1 ? 's' : ''}
</h3>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="text-xs text-slate-500 hover:text-slate-700"
>
Effacer la recherche
</button>
)}
</div>
<div className="space-y-2">
{filteredEmplois.map((emploi, index) => (
<EmploiCard
key={index}
emploi={emploi}
color={getColorForFiliere(emploi.filiere)}
/>
))}
</div>
{filteredEmplois.length === 0 && (
<div className="text-center py-12">
<p className="text-sm text-slate-500">Aucun emploi trouvé pour cette recherche.</p>
</div>
)}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,92 @@
"use client";
import React, { useState } from 'react';
import CategorieBHorsData from './categorie-b-hors-data';
import CategorieBHorsDataPart2 from './categorie-b-hors-data-part2';
import CategorieBHorsDataPart3 from './categorie-b-hors-data-part3';
type Filiere = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I';
const filieres: { code: Filiere; nom: string; part: 1 | 2 | 3 }[] = [
{ code: 'A', nom: 'Contenu du programme et collaboration artistique', part: 1 },
{ code: 'B', nom: 'Costumes, décor', part: 1 },
{ code: 'C', nom: 'Image', part: 1 },
{ code: 'D', nom: 'Plateaux et tournage', part: 2 },
{ code: 'E', nom: 'Postproduction', part: 2 },
{ code: 'F', nom: 'Production', part: 2 },
{ code: 'G', nom: 'Réalisation', part: 3 },
{ code: 'H', nom: 'Son', part: 3 },
{ code: 'I', nom: 'Web', part: 3 },
];
export default function CategorieBHors() {
const [activeFiliere, setActiveFiliere] = useState<Filiere>('A');
const [hoveredFiliere, setHoveredFiliere] = useState<Filiere | null>(null);
const activePart = filieres.find(f => f.code === activeFiliere)?.part || 1;
return (
<div className="space-y-4">
{/* En-tête de la catégorie */}
<div className="rounded-xl border bg-gradient-to-br from-emerald-50 to-teal-50 p-4">
<h2 className="text-lg font-semibold text-emerald-900 mb-1">
Catégorie B - Hors fiction & flux
</h2>
<p className="text-sm text-emerald-700">
Grille des salaires par filière - Programmes ni fiction, ni flux
</p>
{/* Ligne d'information globale supprimée pour alléger l'en-tête */}
</div>
{/* Sélection des filières */}
<div className="space-y-3">
<p className="text-xs text-slate-600">
Sélectionnez une filière pour explorer les emplois :
</p>
<div className="flex flex-wrap gap-2">
{filieres.map((filiere) => (
<div key={filiere.code} className="relative">
<button
onClick={() => setActiveFiliere(filiere.code)}
onMouseEnter={() => setHoveredFiliere(filiere.code)}
onMouseLeave={() => setHoveredFiliere(null)}
className={`relative px-4 py-2 rounded-full text-sm font-semibold transition-all ${
activeFiliere === filiere.code
? 'bg-gradient-to-r from-emerald-500 to-teal-600 text-white shadow-md'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Filière {filiere.code}
{activeFiliere === filiere.code && (
<span className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-white flex items-center justify-center">
<svg className="w-3 h-3 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</span>
)}
</button>
{/* Tooltip */}
{hoveredFiliere === filiere.code && (
<div className="absolute z-50 bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-1.5 bg-slate-900 text-white text-xs font-medium rounded-lg shadow-lg whitespace-nowrap animate-in fade-in slide-in-from-bottom-1 duration-200">
{filiere.nom}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px">
<div className="border-4 border-transparent border-t-slate-900" />
</div>
</div>
)}
</div>
))}
</div>
{/* Contenu */}
<div className="mt-4">
{activePart === 1 && <CategorieBHorsData key={activeFiliere} activeFiliere={activeFiliere} />}
{activePart === 2 && <CategorieBHorsDataPart2 key={activeFiliere} activeFiliere={activeFiliere} />}
{activePart === 3 && <CategorieBHorsDataPart3 key={activeFiliere} activeFiliere={activeFiliere} />}
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,122 @@
"use client";
import React from 'react';
import { Users, Info, Sparkles } from 'lucide-react';
function euro(n: number) {
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(n);
}
const HN_ROLES = [
"Animateur démission",
"Artiste invité",
"Intervenant spécialisé (suivi du nom de la spécialité)",
"Invité / Intervenant",
];
const CACHETS = [
{ emploi: "Doublure lumière", journalier: 122.00 },
{ emploi: "Figurant - Ensemble de 30 personnes ou plus", journalier: 96.00 },
{ emploi: "Figurant - Ensemble de moins de 30 personnes", journalier: 98.00 },
];
const SUPPLEMENTS = [
{ label: "Fourniture de costumes spéciaux (jaquette, costume de service, barman, steward, garçon de café, agent de police, costume d'époque ancienne, costume de soirée, habit, de maître d'hôtel, spencer, smoking, robe du soir, toutes teintes pouvant être exigées)", montant: 47.05 },
{ label: "Silhouettes (artistes de complément dont le personnage doit ressortir dans le champ de la caméra, jusquà 2 répliques)", montant: 42.24 },
{ label: "Essayage", montant: 15.53 },
];
const InfoRow: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="flex items-start gap-2">
<div className="mt-0.5 flex-shrink-0 w-5 h-5 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center">
<Info className="w-3.5 h-3.5 text-slate-600" aria-hidden="true" />
</div>
<p className="text-sm text-slate-600">{children}</p>
</div>
);
export default function CategorieC() {
return (
<div className="space-y-6">
{/* En-tête */}
<section className="rounded-2xl border bg-white p-6">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-gradient-to-br from-fuchsia-500 to-pink-600 flex items-center justify-center">
<Users className="w-6 h-6 text-white" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h2 className="text-xl font-semibold text-slate-900">Catégorie C</h2>
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-semibold bg-gradient-to-r from-fuchsia-500 to-pink-600 text-white">
Intervenants à limage et artistes de complément
</span>
</div>
<p className="text-sm text-slate-600">Salaires applicables au 1er janvier 2025 (Avenant n°20 du 29 novembre 2024, non étendu)</p>
</div>
</div>
</section>
{/* HN roles */}
<section className="rounded-2xl border bg-white p-6">
<h3 className="text-base font-semibold text-slate-900 mb-3 flex items-center gap-2">
<Sparkles className="w-4 h-4 text-fuchsia-600" /> Emplois Hors Niveau (HN)
</h3>
<ul className="list-disc ml-5 space-y-1 text-sm text-slate-700">
{HN_ROLES.map((r, i) => (
<li key={i}>{r} <span className="font-medium">HN</span></li>
))}
</ul>
</section>
{/* Cachets */}
<section className="rounded-2xl border overflow-hidden">
<div className="grid grid-cols-3 text-sm font-semibold text-white">
<div className="bg-fuchsia-600 px-4 py-3">Emploi</div>
<div className="bg-fuchsia-600 px-4 py-3">Cachet minimum journalier</div>
<div className="bg-fuchsia-600 px-4 py-3">Semaine civile (5 jours)</div>
</div>
{CACHETS.map((c, idx) => (
<div key={idx} className={`grid grid-cols-3 text-sm ${idx % 2 === 1 ? 'bg-slate-50' : 'bg-white'}`}>
<div className="border-t px-4 py-3 text-slate-900">{c.emploi}</div>
<div className="border-t px-4 py-3 font-semibold text-slate-900">{euro(c.journalier)}</div>
<div className="border-t px-4 py-3 text-slate-900">{euro(+(c.journalier * 4.5).toFixed(2))}</div>
</div>
))}
<div className="px-4 py-3 border-t bg-slate-50">
<InfoRow>
La demi-journée est rémunérée <strong>65%</strong> du tarif journalier. La semaine civile de 5 jours est rémunérée <strong>4,5 fois</strong> le tarif journalier.
</InfoRow>
</div>
</section>
{/* Définition et suppléments */}
<section className="rounded-2xl border bg-white p-6 space-y-3">
<h3 className="text-base font-semibold text-slate-900">Figurants définitions</h3>
<div className="space-y-2">
<InfoRow>
(1) Ensemble de 30 personnes ou plus, portant costumes tout venant, de correction ordinaire, élégant de ville, tailleur, robe d'après-midi, de cocktail ou de dîner, teinte claire ou foncée pouvant être exigée, d'époque actuelle, avec désignation ou pas de la saison.
</InfoRow>
<InfoRow>
(2) Ensemble de moins de 30 personnes, mêmes conditions vestimentaires que ci-dessus.
</InfoRow>
</div>
<h3 className="text-base font-semibold text-slate-900 mt-2">Suppléments</h3>
<ul className="space-y-2">
{SUPPLEMENTS.map((s, i) => (
<li key={i} className="flex items-start gap-2">
<div className="mt-0.5 flex-shrink-0 w-5 h-5 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center">
<Info className="w-3.5 h-3.5 text-slate-600" aria-hidden="true" />
</div>
<div className="text-sm text-slate-700 flex-1">
{s.label} <span className="font-semibold">{euro(s.montant)}</span>
</div>
</li>
))}
</ul>
</section>
</div>
);
}

View file

@ -0,0 +1,87 @@
"use client";
import React from 'react';
import { Clapperboard, Info } from 'lucide-react';
function euro(value: number): string {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR',
}).format(value);
}
export default function FictionRealisateur() {
const lines = [
{ label: 'Journalier', duree: '9 heures', montant: 300.07 },
{ label: 'Hebdomadaire', duree: '45 heures', montant: 1500.96 },
{ label: 'Mensuel', duree: '45 heures / semaine', montant: 5250.04 },
];
const InfoRow: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="flex items-start gap-2">
<div className="mt-0.5 flex-shrink-0 w-5 h-5 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center">
<Info className="w-3.5 h-3.5 text-slate-600" aria-hidden="true" />
</div>
<p className="text-sm text-slate-600">{children}</p>
</div>
);
return (
<div className="space-y-6">
{/* En-tête */}
<div className="rounded-2xl border bg-white p-6">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-gradient-to-br from-orange-500 to-rose-600 flex items-center justify-center">
<Clapperboard className="w-6 h-6 text-white" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h2 className="text-xl font-semibold text-slate-900">Fiction - Réalisateur</h2>
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-semibold bg-gradient-to-r from-orange-500 to-rose-600 text-white">
Filière G Hors niveau
</span>
</div>
<p className="text-sm text-slate-600">
Annexe A de l'accord du 15 septembre 2023 relatif aux réalisateurs applicable au 1er novembre 2023 (non étendu)
</p>
</div>
</div>
</div>
{/* Tableau */}
<div className="rounded-2xl border overflow-hidden">
<div className="grid grid-cols-4 text-sm font-semibold text-white">
<div className="col-span-1 bg-sky-600 px-4 py-3">Salaire minimum</div>
<div className="col-span-1 bg-sky-600 px-4 py-3">Durée du travail</div>
<div className="col-span-1 bg-sky-600 px-4 py-3">Montant minimum garanti</div>
<div className="col-span-1 bg-sky-600 px-4 py-3">Applicable si</div>
</div>
{lines.map((l, idx) => (
<div key={idx} className={`grid grid-cols-4 text-sm ${idx % 2 === 1 ? 'bg-slate-50' : 'bg-white'}`}>
<div className="col-span-1 border-t px-4 py-3 font-semibold text-slate-900">{l.label}</div>
<div className="col-span-1 border-t px-4 py-3 text-slate-700">{l.duree}</div>
<div className="col-span-1 border-t px-4 py-3 font-semibold text-slate-900">{euro(l.montant)}</div>
<div className="col-span-1 border-t px-4 py-3 text-slate-700">
{l.label === 'Journalier' && (<span>Engagement &lt; 5 jours sur une même semaine calendaire</span>)}
{l.label === 'Hebdomadaire' && (<span>Engagement 5 jours sur la même semaine calendaire</span>)}
{l.label === 'Mensuel' && (<span>Engagement 13 semaines réparties sur 4 mois consécutifs</span>)}
</div>
</div>
))}
</div>
{/* Notes */}
<div className="rounded-2xl border bg-white p-4 space-y-3">
<InfoRow>
Depuis la loi du 7 juillet 2016, les réalisateurs relèvent du statut dartistes du spectacle (art. L7121-2 du Code du travail) et de lannexe X du règlement général dassurance chômage.
</InfoRow>
<InfoRow>
La rémunération peut être établie en cachets ou en heures. Les montants indiqués sont des minima garantis, même si la durée effective natteint pas 9h ou 45h.
</InfoRow>
<InfoRow>
Laccord du 15 septembre 2023 précise la fonction et les conditions dengagement des réalisateurs. Lannexe A fixe les minima spécifiques aux réalisateurs de fictions audiovisuelles. Application à compter du 1er novembre 2023 (non étendu).
</InfoRow>
</div>
</div>
);
}

View file

@ -0,0 +1,351 @@
"use client";
import React, { useEffect, useState } from 'react';
import { usePageTitle } from '@/hooks/usePageTitle';
import Link from 'next/link';
import { ArrowLeft, Scale, Calculator, X } from 'lucide-react';
import CategorieAContent from './categorie-a-data';
import CategorieBFiction from './categorie-b-fiction';
import CategorieBFlux from './categorie-b-flux';
import CategorieBHors from './categorie-b-hors';
import FictionRealisateur from './fiction-realisateur';
import CategorieC from './categorie-c';
import ArtistesMusiciens from './artistes-musiciens';
import ArtistesInterpretes from './artistes-interpretes';
import SimulateurContent from '@/components/simulateur/SimulateurContent';
import CalculatorComponent from '@/components/Calculator';
import { useDraggableModal } from '@/hooks/useDraggableModal';
export default function CCNPAPage() {
usePageTitle("Minima CCNPA");
const [isSimulateurOpen, setIsSimulateurOpen] = useState(false);
const [isCalculatorOpen, setIsCalculatorOpen] = useState(false);
const [modalPosition, setModalPosition] = useState({ x: 0, y: 0 });
const modalRef = React.useRef<HTMLDivElement>(null);
// Gestion du drag & drop du modal
const { onPointerDown, onPointerMove, onPointerUp } = useDraggableModal(
modalRef,
setModalPosition,
{ constrainToViewport: true, disableIframeDuringDrag: true }
);
// Écouter les messages de l'iframe pour ouvrir la calculatrice
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data?.type === 'openCalculator') {
setIsCalculatorOpen(true);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
useEffect(() => {
// Script de gestion des onglets
const tabs = Array.from(document.querySelectorAll('[role="tab"]'));
const panels = Array.from(document.querySelectorAll('.ccnpa-panel'));
function activateTab(tab: Element) {
tabs.forEach((t) => {
const selected = t === tab;
t.setAttribute('aria-selected', selected ? 'true' : 'false');
(t as HTMLElement).tabIndex = selected ? 0 : -1;
});
panels.forEach((p) => {
const active = p.id === tab.getAttribute('aria-controls');
p.classList.toggle('active', active);
if (active) {
p.removeAttribute('hidden');
} else {
p.setAttribute('hidden', '');
}
});
(tab as HTMLElement).focus({ preventScroll: true });
}
tabs.forEach((tab) => {
tab.addEventListener('click', () => activateTab(tab));
});
// Navigation clavier
document.addEventListener('keydown', (e) => {
const current = document.querySelector('[role="tab"][aria-selected="true"]');
if (!current) return;
const i = tabs.indexOf(current);
if (e.key === 'ArrowRight') {
e.preventDefault();
activateTab(tabs[(i + 1) % tabs.length]);
}
if (e.key === 'ArrowLeft') {
e.preventDefault();
activateTab(tabs[(i - 1 + tabs.length) % tabs.length]);
}
if (e.key === 'Home') {
e.preventDefault();
activateTab(tabs[0]);
}
if (e.key === 'End') {
e.preventDefault();
activateTab(tabs[tabs.length - 1]);
}
});
}, []);
return (
<div className="space-y-6">
<style jsx global>{`
.ccnpa-tabs [role="tablist"] {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 20px;
}
.ccnpa-tabs [role="tab"] {
appearance: none;
border: 1px solid #e2e8f0;
background: white;
border-radius: 999px;
padding: 9px 16px;
cursor: pointer;
font-weight: 600;
font-size: 14px;
color: #475569;
transition: all 0.2s;
}
.ccnpa-tabs [role="tab"]:hover {
background: #f8fafc;
border-color: #cbd5e1;
}
.ccnpa-tabs [role="tab"][aria-selected="true"] {
background: linear-gradient(135deg, #0ea5e9 0%, #06b6d4 100%);
border-color: #06b6d4;
color: white;
box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
}
.ccnpa-tabs [role="tab"]:focus-visible {
outline: 2px solid #0ea5e9;
outline-offset: 2px;
}
.ccnpa-panel {
display: none;
}
.ccnpa-panel.active {
display: block;
}
`}</style>
{/* Navigation retour */}
<Link
href="/minima-ccn"
className="inline-flex items-center gap-2 text-sm text-slate-600 hover:text-slate-900 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
Retour aux minima CCN
</Link>
{/* En-tête */}
<section className="rounded-2xl border bg-white p-6">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 w-12 h-12 rounded-xl bg-gradient-to-br from-cyan-500 to-blue-600 flex items-center justify-center">
<Scale className="w-6 h-6 text-white" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h1 className="text-2xl font-semibold text-slate-900">CCNPA (IDCC 2642)</h1>
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-gradient-to-r from-emerald-500 to-emerald-600 text-white shadow-sm">
À jour 2025
</span>
</div>
<p className="text-sm text-slate-600 mb-2">
Convention Collective Nationale de la Production Audiovisuelle
</p>
<p className="text-xs text-slate-500">
Grille des minima conventionnels pour les salariés de la production audiovisuelle.
<br />
<strong>Tous les montants sont exprimés bruts.</strong>
</p>
</div>
</div>
</section>
{/* Onglets */}
<section className="rounded-2xl border bg-white p-6 ccnpa-tabs">
<p className="text-sm text-slate-600 mb-4">
Cliquez sur un onglet pour accéder aux minima par catégorie professionnelle.
</p>
<div role="tablist" aria-label="Onglets CCNPA">
<button
role="tab"
aria-selected="true"
aria-controls="ccnpa-categorie-a"
tabIndex={0}
>
Catégorie A
</button>
<button
role="tab"
aria-selected="false"
aria-controls="ccnpa-categorie-b-fiction"
tabIndex={-1}
>
Catégorie B - Fiction
</button>
<button
role="tab"
aria-selected="false"
aria-controls="ccnpa-categorie-b-flux"
tabIndex={-1}
>
Catégorie B - Flux
</button>
<button
role="tab"
aria-selected="false"
aria-controls="ccnpa-categorie-b-hors"
tabIndex={-1}
>
Catégorie B - Hors fiction & flux
</button>
<button
role="tab"
aria-selected="false"
aria-controls="ccnpa-categorie-c"
tabIndex={-1}
>
Catégorie C
</button>
<button
role="tab"
aria-selected="false"
aria-controls="ccnpa-artistes-musiciens"
tabIndex={-1}
>
Artistes Musiciens
</button>
<button
role="tab"
aria-selected="false"
aria-controls="ccnpa-artistes-interpretes"
tabIndex={-1}
>
Artistes Interprètes
</button>
<button
role="tab"
aria-selected="false"
aria-controls="ccnpa-fiction-realisateur"
tabIndex={-1}
>
Fiction - Réalisateur
</button>
</div>
{/* Panneaux */}
<div id="ccnpa-categorie-a" className="ccnpa-panel active" role="tabpanel">
<CategorieAContent />
</div>
<div id="ccnpa-categorie-b-fiction" className="ccnpa-panel" role="tabpanel" hidden>
<CategorieBFiction />
</div>
<div id="ccnpa-categorie-b-flux" className="ccnpa-panel" role="tabpanel" hidden>
<CategorieBFlux />
</div>
<div id="ccnpa-categorie-b-hors" className="ccnpa-panel" role="tabpanel" hidden>
<CategorieBHors />
</div>
<div id="ccnpa-categorie-c" className="ccnpa-panel" role="tabpanel" hidden>
<CategorieC />
</div>
<div id="ccnpa-fiction-realisateur" className="ccnpa-panel" role="tabpanel" hidden>
<FictionRealisateur />
</div>
<div id="ccnpa-artistes-musiciens" className="ccnpa-panel" role="tabpanel" hidden>
<ArtistesMusiciens />
</div>
<div id="ccnpa-artistes-interpretes" className="ccnpa-panel" role="tabpanel" hidden>
<ArtistesInterpretes />
</div>
</section>
{/* Bouton flottant Simulateur */}
<button
onClick={() => setIsSimulateurOpen(!isSimulateurOpen)}
className="fixed bottom-6 right-6 z-50 inline-flex items-center gap-2 px-5 py-3 rounded-full text-sm font-bold bg-gradient-to-r from-amber-400 to-amber-500 text-slate-900 hover:from-amber-500 hover:to-amber-600 transition-all shadow-lg hover:shadow-xl hover:scale-105"
>
<Calculator className="w-5 h-5" />
Simulateur
</button>
{/* Modale compacte qui sort du bouton */}
{isSimulateurOpen && (
<>
{/* Modale compacte déplaçable */}
<div
ref={modalRef}
className="fixed z-50 w-[500px] max-h-[680px] bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden"
style={{
left: modalPosition.x !== 0 ? `${modalPosition.x}px` : 'auto',
top: modalPosition.y !== 0 ? `${modalPosition.y}px` : 'auto',
bottom: modalPosition.x === 0 && modalPosition.y === 0 ? '6rem' : 'auto',
right: modalPosition.x === 0 && modalPosition.y === 0 ? '1.5rem' : 'auto',
}}
>
{/* Header draggable */}
<div
className="flex items-center justify-between px-4 py-3 border-b bg-gradient-to-r from-amber-50 to-orange-50 cursor-move select-none touch-none"
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
>
<h3 className="text-sm font-bold text-slate-900 flex items-center gap-2">
<Calculator className="w-4 h-4 text-amber-600" />
Simulateur de paie
</h3>
<button
onClick={() => {
setIsSimulateurOpen(false);
setModalPosition({ x: 0, y: 0 }); // Réinitialiser la position
}}
className="p-1.5 rounded-lg hover:bg-slate-100 transition-colors"
aria-label="Fermer le simulateur"
onPointerDown={(e) => e.stopPropagation()}
>
<X className="w-4 h-4 text-slate-600" />
</button>
</div>
{/* Content avec scroll */}
<div className="flex-1 overflow-auto p-4">
<SimulateurContent hideInfoPanel />
</div>
</div>
</>
)}
{/* Calculatrice globale */}
<CalculatorComponent
isOpen={isCalculatorOpen}
onClose={() => setIsCalculatorOpen(false)}
/>
</div>
);
}

View file

@ -13,65 +13,34 @@ import Annexe5Content from './annexe5-data';
import Annexe6Content from './annexe6-data';
import SimulateurContent from '@/components/simulateur/SimulateurContent';
import CalculatorComponent from '@/components/Calculator';
import { useDraggableModal } from '@/hooks/useDraggableModal';
export default function CCNSVPPage() {
usePageTitle("Minima CCNSVP");
const [isSimulateurOpen, setIsSimulateurOpen] = useState(false);
const [isCalculatorOpen, setIsCalculatorOpen] = useState(false);
const [modalPosition, setModalPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const modalRef = React.useRef<HTMLDivElement>(null);
// Etat de drag performant (hors React)
const dragState = React.useRef<{
dragging: boolean;
startX: number;
startY: number;
origLeft: number;
origTop: number;
dx: number;
dy: number;
raf: number | null;
iframe: HTMLIFrameElement | null;
}>({ dragging: false, startX: 0, startY: 0, origLeft: 0, origTop: 0, dx: 0, dy: 0, raf: null, iframe: null });
// Gestion du drag & drop du modal
const handleMouseDown = (e: React.MouseEvent) => {
if (!modalRef.current) return;
// Si c'est le premier drag, calculer la position réelle du modal
if (modalPosition.x === 0 && modalPosition.y === 0) {
const rect = modalRef.current.getBoundingClientRect();
setModalPosition({ x: rect.left, y: rect.top });
setDragOffset({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
} else {
setDragOffset({
x: e.clientX - modalPosition.x,
y: e.clientY - modalPosition.y,
});
}
setIsDragging(true);
};
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging) return;
const newX = e.clientX - dragOffset.x;
const newY = e.clientY - dragOffset.y;
setModalPosition({
x: newX,
y: newY,
});
};
const handleMouseUp = () => {
setIsDragging(false);
};
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, dragOffset]);
const { onPointerDown, onPointerMove, onPointerUp } = useDraggableModal(
modalRef,
setModalPosition,
{ constrainToViewport: true, disableIframeDuringDrag: true }
);
// Écouter les messages de l'iframe pour ouvrir la calculatrice
useEffect(() => {
@ -594,31 +563,23 @@ export default function CCNSVPPage() {
{/* Modale compacte qui sort du bouton */}
{isSimulateurOpen && (
<>
{/* Overlay transparent pour fermer au clic */}
<div
className="fixed inset-0 z-40"
onClick={() => {
setIsSimulateurOpen(false);
setModalPosition({ x: 0, y: 0 }); // Réinitialiser la position
}}
/>
{/* Modale compacte déplaçable */}
<div
ref={modalRef}
className="fixed z-50 w-[500px] max-h-[600px] bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden animate-in slide-in-from-bottom-4 duration-300"
className="fixed z-50 w-[500px] max-h-[680px] bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden"
style={{
left: modalPosition.x !== 0 ? `${modalPosition.x}px` : 'auto',
top: modalPosition.y !== 0 ? `${modalPosition.y}px` : 'auto',
bottom: modalPosition.x === 0 && modalPosition.y === 0 ? '6rem' : 'auto',
right: modalPosition.x === 0 && modalPosition.y === 0 ? '1.5rem' : 'auto',
}}
onClick={(e) => e.stopPropagation()}
>
{/* Header draggable */}
<div
className="flex items-center justify-between px-4 py-3 border-b bg-gradient-to-r from-amber-50 to-orange-50 cursor-move select-none"
onMouseDown={handleMouseDown}
className="flex items-center justify-between px-4 py-3 border-b bg-gradient-to-r from-amber-50 to-orange-50 cursor-move select-none touch-none"
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
>
<h3 className="text-sm font-bold text-slate-900 flex items-center gap-2">
<Calculator className="w-4 h-4 text-amber-600" />
@ -631,7 +592,7 @@ export default function CCNSVPPage() {
}}
className="p-1.5 rounded-lg hover:bg-slate-100 transition-colors"
aria-label="Fermer le simulateur"
onMouseDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<X className="w-4 h-4 text-slate-600" />
</button>

View file

@ -91,33 +91,37 @@ export default function MinimaCCNPage() {
</div>
</Link>
{/* CCNPA - Bientôt disponible */}
<div className="relative rounded-2xl border border-slate-200 bg-slate-50 p-6 opacity-60 cursor-not-allowed">
{/* CCNPA */}
<Link
href="/minima-ccn/ccnpa"
className="group relative rounded-2xl border border-slate-200 bg-white p-6 transition-all duration-200 hover:shadow-lg hover:border-cyan-300 hover:-translate-y-1"
>
{/* Badge */}
<div className="absolute top-4 right-4">
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-gradient-to-r from-slate-400 to-slate-500 text-white shadow-sm">
Bientôt disponible
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-gradient-to-r from-emerald-500 to-emerald-600 text-white shadow-sm">
À jour 2025
</span>
</div>
{/* Icône */}
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-slate-200 to-slate-300 border border-slate-300 flex items-center justify-center mb-4">
<Video className="w-7 h-7 text-slate-500" strokeWidth={1.6} />
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-cyan-100 to-blue-100 border border-cyan-200 flex items-center justify-center mb-4">
<Video className="w-7 h-7 text-cyan-600" strokeWidth={1.6} />
</div>
{/* Contenu */}
<h2 className="text-xl font-semibold text-slate-700 mb-2">
<h2 className="text-xl font-semibold text-slate-900 mb-2">
CCNPA (IDCC 2642)
</h2>
<p className="text-sm text-slate-500 mb-4">
<p className="text-sm text-slate-600 mb-4">
Convention Collective Nationale de la Production Audiovisuelle
</p>
{/* CTA */}
<div className="inline-flex items-center gap-2 text-sm font-medium text-slate-400">
En préparation
<div className="inline-flex items-center gap-2 text-sm font-semibold text-cyan-600 group-hover:gap-3 transition-all">
Voir les minima
<ExternalLink className="w-4 h-4" />
</div>
</div>
</Link>
</section>
</div>
);

View file

@ -191,10 +191,11 @@ export default function Calculator({ isOpen, onClose, onUseResult }: CalculatorP
const calculatorRef = React.useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen && calculatorRef.current) {
// Focus une fois que le composant est monté (position non nulle)
if (isOpen && position !== null && calculatorRef.current) {
calculatorRef.current.focus();
}
}, [isOpen]);
}, [isOpen, position]);
if (!isOpen || position === null) return null;
@ -228,7 +229,13 @@ export default function Calculator({ isOpen, onClose, onUseResult }: CalculatorP
{/* Body */}
<div className="p-4">
{/* Display */}
<div className="bg-gradient-to-br from-slate-100 to-slate-200 border-2 border-slate-300 rounded-xl p-4 text-right text-2xl font-semibold text-slate-900 mb-4 min-h-[50px] break-all">
<div className="relative bg-gradient-to-br from-slate-100 to-slate-200 border-2 border-slate-300 rounded-xl p-4 text-right text-2xl font-semibold text-slate-900 mb-4 min-h-[50px] break-all">
{/* Opérateur courant (badge) */}
{operator && (
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-sm font-semibold text-slate-600 select-none">
{operator === '/' ? '÷' : operator === '*' ? '×' : operator === '-' ? '' : operator}
</span>
)}
{display || '0'}
</div>

View file

@ -81,6 +81,9 @@ export default function SimulateurContent({ hideInfoPanel = false }: SimulateurC
border: none;
background: white;
}
.simulateur-iframe-sidebar.compact {
height: 420px; /* Taille plus compacte pour le modal */
}
`}</style>
{/* Layout avec simulateur et info */}
@ -89,8 +92,8 @@ export default function SimulateurContent({ hideInfoPanel = false }: SimulateurC
{/* Simulateur principal */}
<div className="flex-1 min-w-0">
<iframe
src="/simulateur-embed.html"
className="simulateur-iframe-sidebar"
src={hideInfoPanel ? "/simulateur-embed-compact.html" : "/simulateur-embed.html"}
className={hideInfoPanel ? "simulateur-iframe-sidebar compact" : "simulateur-iframe-sidebar"}
title="Simulateur de paie intermittent"
loading="lazy"
/>

126
hooks/useDraggableModal.ts Normal file
View file

@ -0,0 +1,126 @@
"use client";
import React from 'react';
type SetPos = React.Dispatch<React.SetStateAction<{ x: number; y: number }>>;
export interface DraggableOptions {
constrainToViewport?: boolean;
disableIframeDuringDrag?: boolean;
}
export function useDraggableModal(
modalRef: React.RefObject<HTMLDivElement | null>,
setModalPosition: SetPos,
options: DraggableOptions = { constrainToViewport: true, disableIframeDuringDrag: true }
) {
const dragState = React.useRef<{
dragging: boolean;
startX: number;
startY: number;
origLeft: number;
origTop: number;
dx: number;
dy: number;
width: number;
height: number;
raf: number | null;
iframe: HTMLIFrameElement | null;
}>({ dragging: false, startX: 0, startY: 0, origLeft: 0, origTop: 0, dx: 0, dy: 0, width: 0, height: 0, raf: null, iframe: null });
const pointerDown = (e: React.PointerEvent) => {
if (!modalRef.current) return;
const rect = modalRef.current.getBoundingClientRect();
dragState.current.dragging = true;
dragState.current.startX = e.clientX;
dragState.current.startY = e.clientY;
dragState.current.origLeft = rect.left;
dragState.current.origTop = rect.top;
dragState.current.dx = 0;
dragState.current.dy = 0;
dragState.current.width = rect.width;
dragState.current.height = rect.height;
// Improve paint performance
modalRef.current.style.willChange = 'transform';
// Ensure no conflicting transform animation
modalRef.current.style.animation = 'none';
// Optionally disable iframe interactions during drag
if (options.disableIframeDuringDrag) {
const iframe = modalRef.current.querySelector('iframe');
if (iframe) {
dragState.current.iframe = iframe as HTMLIFrameElement;
dragState.current.iframe.style.pointerEvents = 'none';
} else {
dragState.current.iframe = null;
}
}
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
};
const pointerMove = (e: React.PointerEvent) => {
if (!dragState.current.dragging || !modalRef.current) return;
// Raw delta
let dx = e.clientX - dragState.current.startX;
let dy = e.clientY - dragState.current.startY;
if (options.constrainToViewport) {
const maxLeft = Math.max(0, window.innerWidth - dragState.current.width);
const maxTop = Math.max(0, window.innerHeight - dragState.current.height);
const wantedLeft = dragState.current.origLeft + dx;
const wantedTop = dragState.current.origTop + dy;
const clampedLeft = Math.min(Math.max(0, wantedLeft), maxLeft);
const clampedTop = Math.min(Math.max(0, wantedTop), maxTop);
dx = clampedLeft - dragState.current.origLeft;
dy = clampedTop - dragState.current.origTop;
}
dragState.current.dx = dx;
dragState.current.dy = dy;
if (dragState.current.raf) cancelAnimationFrame(dragState.current.raf);
dragState.current.raf = requestAnimationFrame(() => {
if (!modalRef.current) return;
modalRef.current.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
});
};
const pointerUp = (e: React.PointerEvent) => {
if (!dragState.current.dragging) return;
dragState.current.dragging = false;
// Snap to the exact visual rect before clearing transform
if (modalRef.current) {
const rectNow = modalRef.current.getBoundingClientRect();
modalRef.current.style.left = `${rectNow.left}px`;
modalRef.current.style.top = `${rectNow.top}px`;
modalRef.current.style.right = 'auto';
modalRef.current.style.bottom = 'auto';
setModalPosition({ x: rectNow.left, y: rectNow.top });
}
if (dragState.current.raf) {
cancelAnimationFrame(dragState.current.raf);
dragState.current.raf = null;
}
if (modalRef.current) {
modalRef.current.style.transform = '';
modalRef.current.style.willChange = '';
}
if (dragState.current.iframe) {
dragState.current.iframe.style.pointerEvents = '';
dragState.current.iframe = null;
}
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
};
return {
onPointerDown: pointerDown,
onPointerMove: pointerMove,
onPointerUp: pointerUp,
} as const;
}

View file

@ -0,0 +1,222 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Simulateur Paie Compact</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
<style>
:root { color-scheme: light; }
body { margin: 0; background: #f9fafb; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
.container-compact { padding: 8px; }
.card-compact { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 10px; }
.grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 6px; align-items: start; }
.grid .rowpair { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.grid .span2 { grid-column: span 2; }
.grid .span3 { grid-column: 1 / -1; }
.align-bottom { align-self: end; }
.results-grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 8px; }
.result-tile { background: #fff; border: 1px solid #e5e7eb; border-radius: 8px; padding: 8px; }
.result-tile .label { font-size: 11px; color: #64748b; margin-bottom: 2px; }
.result-tile .value { font-weight: 700; color: #0f172a; }
.grid select.form-select { min-width: 0; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
label { font-size: 12px; font-weight: 600; margin: 0; }
input.form-control, select.form-select { font-size: 12px; min-height: 32px; }
.options { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 8px; }
.options label { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 8px; background: #fff; margin: 0; }
.options input[type="radio"] { accent-color: #6366f1; }
.options label.checked { background: #eef2ff; border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99,102,241,0.08); }
#results { font-size: 12px; }
#calcBtn { font-size: 12px; padding: 6px 10px; }
/* Keep 3 columns for modal (~500px). Collapse to 1 only on very small widths. */
@media (max-width: 360px) { .grid { grid-template-columns: 1fr; } .options { grid-template-columns: 1fr !important; } }
</style>
</head>
<body>
<div class="container-compact">
<div class="card-compact">
<div class="grid">
<div class="rowpair">
<label for="conventionSelect">Convention Collective</label>
<select id="conventionSelect" class="form-select form-select-sm">
<option value="1285">1285 Entreprises Artistiques & Culturelles (CCNEAC)</option>
<option value="3090">3090 Spectacle Vivant Privé (CCNSVP)</option>
<option value="1518">1518 ÉCLAT (ex-Animation)</option>
<option value="1922">1922 Radiodiffusion</option>
<option value="2121">2121 Édition phonographique</option>
<option value="2412">2412 Production de films d'animation</option>
<option value="2642">2642 Production audiovisuelle</option>
<option value="3097">3097 Production cinématographique</option>
<option value="3241">3241 Télédiffusion</option>
<option value="3252">3252 Entreprises au service de la création et de l'événement</option>
</select>
</div>
<div class="rowpair">
<label for="categorieSelect">Catégorie</label>
<select id="categorieSelect" class="form-select form-select-sm">
<option value="artiste" selected>Artiste (Annexe 10)</option>
<option value="technicien">Technicien (Annexe 8)</option>
</select>
</div>
<div class="rowpair">
<label for="statutSelect">Statut</label>
<select id="statutSelect" class="form-select form-select-sm">
<option value="non-cadre" selected>Artiste non-cadre</option>
<option value="cadre">Artiste cadre</option>
</select>
</div>
</div>
</div>
<div class="card-compact" id="abattementCard" style="display:none; margin-top:8px;">
<div class="grid">
<div class="rowpair span3">
<label>Abattement pour frais professionnels</label>
<div class="d-flex gap-3">
<label class="d-inline-flex align-items-center gap-2"><input type="radio" name="abattement" value="oui"> Oui</label>
<label class="d-inline-flex align-items-center gap-2"><input type="radio" name="abattement" value="non" checked> Non</label>
</div>
</div>
<div class="rowpair span3" id="professionBlock" style="display:none;">
<label for="professionSelect">Profession du salarié</label>
<select id="professionSelect" class="form-select form-select-sm">
<option value="">-- Sélectionnez une profession --</option>
<option value="drama">Artiste dramatique / lyrique / cinématographique / chorégraphique / de cirque (21%)</option>
<option value="musique">Artiste musicien / choriste / chef d'orchestre (18%)</option>
</select>
</div>
</div>
</div>
<div class="card-compact" style="margin-top:8px;">
<div class="grid">
<div class="rowpair">
<label for="cachetsInput">Nombre de cachets</label>
<input id="cachetsInput" type="number" step="1" placeholder="Ex : 10" class="form-control form-control-sm" />
</div>
<div class="rowpair">
<label for="heuresInput">Nombre d'heures</label>
<input id="heuresInput" type="number" step="0.1" placeholder="Ex : 35" class="form-control form-control-sm" />
</div>
<div class="rowpair">
<label for="datesInput">Dates de travail</label>
<input id="datesInput" type="text" placeholder="Cliquez pour sélectionner des dates" class="form-control form-control-sm" readonly />
</div>
<div class="rowpair span2">
<label for="montantInput">Montant total (€)</label>
<input id="montantInput" type="number" step="0.01" placeholder="Ex: 2000" class="form-control form-control-sm" />
</div>
<div class="rowpair align-bottom">
<button id="openCalculatorBtn" type="button" class="btn btn-light btn-sm w-100">Calculatrice</button>
</div>
</div>
</div>
<div class="card-compact" style="margin-top:8px;">
<div class="grid">
<div class="rowpair span3">
<div class="options" style="display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 8px;">
<label><input type="radio" name="type" value="brut" checked /> Brut</label>
<label><input type="radio" name="type" value="net" /> Net avt PAS</label>
<label><input type="radio" name="type" value="cost" /> Coût employeur</label>
</div>
</div>
<div class="rowpair span3">
<button id="calcBtn" class="btn btn-primary w-100">Calculer</button>
</div>
</div>
</div>
<div class="card-compact" style="margin-top:8px;">
<div id="results"></div>
</div>
</div>
<!-- Hidden full engine iframe -->
<iframe id="engine" src="/simulateur-embed.html" style="display:none; width:0; height:0; border:0;"></iframe>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
// Wire compact form to hidden engine via postMessage API
const engine = document.getElementById('engine');
let fp;
function sendToEngine() {
const payload = {
type: 'simulateur_compact_submit',
data: {
ccn: document.getElementById('conventionSelect').value,
categorie: document.getElementById('categorieSelect').value,
statut: document.getElementById('statutSelect').value,
abattement: (document.querySelector('input[name="abattement"]:checked')||{}).value,
profession: document.getElementById('professionSelect')?.value || '',
cachets: document.getElementById('cachetsInput').value,
heures: document.getElementById('heuresInput').value,
dates: document.getElementById('datesInput').value,
montant: document.getElementById('montantInput').value,
type: (document.querySelector('input[name="type"]:checked')||{}).value || 'brut',
}
};
engine.contentWindow?.postMessage(payload, '*');
}
function renderResults(data){
const fmt = (n)=> Number(n||0).toLocaleString('fr-FR',{minimumFractionDigits:2, maximumFractionDigits:2});
const r = document.getElementById('results');
r.innerHTML = `
<div class="results-grid">
<div class="result-tile">
<div class="label">Brut</div>
<div class="value">€ ${fmt(data.resultat_brut)}</div>
</div>
<div class="result-tile">
<div class="label">Net</div>
<div class="value">€ ${fmt(data.resultat_net)}</div>
</div>
<div class="result-tile">
<div class="label">Coût employeur</div>
<div class="value">€ ${fmt(data.resultat_cost)}</div>
</div>
</div>`;
}
// Listen for calculation results proxied by engine
window.addEventListener('message', (ev) => {
if (ev.data?.type === 'simulateur_calculation') {
renderResults(ev.data.data || {});
}
});
// Compact form behavior
document.getElementById('categorieSelect').addEventListener('change', () => {
const isTech = document.getElementById('categorieSelect').value === 'technicien';
document.getElementById('abattementCard').style.display = isTech ? 'none' : '';
});
document.querySelectorAll('input[name="abattement"]').forEach(r => r.addEventListener('change', () => {
const val = (document.querySelector('input[name="abattement"]:checked')||{}).value;
document.getElementById('professionBlock').style.display = (val === 'oui') ? '' : 'none';
}));
// Flatpickr
fp = flatpickr('#datesInput', { mode: 'multiple', dateFormat: 'Y-m-d' });
// Calculate
document.getElementById('calcBtn').addEventListener('click', () => {
sendToEngine();
});
// Reflect checked styling on radios
document.querySelectorAll('.options input[type="radio"]').forEach(input => {
input.addEventListener('change', () => {
document.querySelectorAll('.options label').forEach(l => l.classList.remove('checked'));
input.closest('label')?.classList.add('checked');
});
});
// Open external calculator through parent
document.getElementById('openCalculatorBtn')?.addEventListener('click', () => {
window.parent?.postMessage({ type: 'openCalculator' }, '*');
});
</script>
</body>
</html>

View file

@ -9,6 +9,129 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Compact mode styles applied when body has .compact-mode (triggered by ?compact=1) */
body.compact-mode {
padding: 8px !important;
font-size: 12px;
}
body.compact-mode .simulateur {
--c-gap: 8px;
--c-gap-small: 6px;
--c-radius: 10px;
--c-border: #e2e8f0;
--c-text: #0f172a;
--c-muted: #64748b;
gap: var(--c-gap);
}
body.compact-mode .form-section {
margin-bottom: 6px;
}
/* 2-colonnes en compact, avec wrappers de paires label+champ */
body.compact-mode .form-section {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
column-gap: 8px;
row-gap: 4px;
align-items: start;
grid-auto-rows: min-content;
background: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 10px;
}
body.compact-mode .form-section .form-row { display: flex; flex-direction: column; min-width: 0; width: 100%; gap: 2px; }
/* Éléments qui doivent occuper toute la largeur */
body.compact-mode .options,
body.compact-mode #calcBtn,
body.compact-mode #openCalculatorBtn,
body.compact-mode #result,
body.compact-mode .detail-table,
body.compact-mode #abattementSectionWrapper { grid-column: 1 / -1; }
/* Réduire l'espacement entre les éléments d'une même section */
body.compact-mode .form-section > * { margin-bottom: 0 !important; }
body.compact-mode label {
font-size: 12px;
font-weight: 600;
margin-bottom: 2px;
}
body.compact-mode input[type="number"],
body.compact-mode input[type="text"],
body.compact-mode select {
font-size: 12px;
padding: 4px 8px;
height: auto; /* let Bootstrap control the height */
min-height: 31px;
line-height: 1.3;
color: #111827; /* slate-900 for contrast */
width: 100%;
min-width: 0;
box-sizing: border-box;
}
/* Améliorer la lisibilité du texte sélectionné dans les selects */
body.compact-mode select {
padding-right: 28px; /* room for chevron */
font-size: 12px;
line-height: 1.3;
height: auto;
min-height: 31px;
background-color: #ffffff;
color: #111827;
appearance: auto;
-webkit-appearance: menulist; /* Safari fix: ensure native dropdown rendering */
text-overflow: clip; /* avoid hidden text issues */
overflow: visible;
}
body.compact-mode select option { font-size: 12px; }
body.compact-mode .options label {
font-size: 12px;
}
body.compact-mode #openCalculatorBtn {
font-size: 10px !important;
padding: 1px 5px !important;
margin-top: 2px !important;
}
body.compact-mode #calcBtn {
font-size: 12px;
padding: 5px 8px;
}
body.compact-mode .result h4 {
font-size: 13px;
margin: 6px 0;
}
body.compact-mode #result {
font-size: 12px;
}
/* Hide detail table in compact */
body.compact-mode #detailTable {
display: none !important;
}
/* Reduce visual clutter: hide popover info icons in compact */
body.compact-mode [data-bs-toggle="popover"] {
display: none;
}
/* Diminuer le spacing des groupes Bootstrap dans la section abattement */
body.compact-mode .d-flex.mb-3 {
margin-bottom: 4px !important;
gap: 6px !important;
}
/* Hide helper paragraphs and reduce section paddings */
body.compact-mode p#daysCount { display: none; }
body.compact-mode .simulateur h1, body.compact-mode h2, body.compact-mode h3, body.compact-mode h4 { margin: 6px 0; }
/* Layout tweaks: simple, tidy options row */
body.compact-mode .options { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
body.compact-mode .options label { margin: 0; display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--c-border); background: #fff; }
body.compact-mode .options input[type="radio"] { accent-color: #6366f1; }
body.compact-mode .options label:has(input[type="radio"]:checked) { background: #eef2ff; border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99,102,241,0.08); }
/* Compact calculator modal inside embed if used */
body.compact-mode #calculator { transform: scale(0.9); transform-origin: top right; }
/* Responsive fallback: single column if space is too narrow */
@media (max-width: 480px) {
body.compact-mode .form-section { grid-template-columns: 1fr; }
body.compact-mode .options { grid-template-columns: 1fr; }
}
</style>
</head>
<body style="margin:0;padding:20px;background:#f9fafb;font-family:system-ui,-apple-system,sans-serif;">
@ -210,10 +333,64 @@
<script>
/* === UI init === */
document.addEventListener('DOMContentLoaded', function () {
// Enable compact mode if requested via query string
try {
const params = new URLSearchParams(window.location.search);
if (params.get('compact') === '1') {
document.body.classList.add('compact-mode');
}
} catch (e) { /* ignore */ }
// En mode compact, regrouper chaque paire Label + (Select/Input) dans un wrapper .form-row
try {
if (document.body.classList.contains('compact-mode')) {
const sections = Array.from(document.querySelectorAll('.simulateur .form-section'));
sections.forEach(section => {
if (section.id === 'abattementSectionWrapper') return; // cette section gère son propre layout
const children = Array.from(section.children);
for (let i = 0; i < children.length; i++) {
const el = children[i];
if (!el || el.classList?.contains('form-row')) continue;
if (el.matches('.options, #calcBtn, #openCalculatorBtn, #result, .detail-table')) continue;
if (el.id === 'cachetsGroup') { el.classList.add('form-row'); continue; }
if (el.tagName && el.tagName.toLowerCase() === 'label') {
const next = children[i + 1];
const wrapper = document.createElement('div');
wrapper.className = 'form-row';
section.insertBefore(wrapper, el);
wrapper.appendChild(el);
if (next && /^(select|input|button)$/i.test(next.tagName)) {
wrapper.appendChild(next);
if (next.id === 'datesInput' || next.id === 'conventionSelect' || next.id === 'professionSelect') {
wrapper.style.gridColumn = '1 / -1';
}
i++;
}
}
}
});
}
} catch (_) { /* ignore compact wrapping errors */ }
// Popovers
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
popoverTriggerList.map(function (el) { return new bootstrap.Popover(el, { html: true }); });
// Harmonise les champs avec Bootstrap pour un rendu pro en compact
try {
if (document.body.classList.contains('compact-mode')) {
document.querySelectorAll('input[type="text"], input[type="number"]').forEach(el => {
el.classList.add('form-control', 'form-control-sm');
});
document.querySelectorAll('select').forEach(el => {
el.classList.add('form-select', 'form-select-sm');
});
document.querySelectorAll('label').forEach(el => {
el.classList.add('form-label');
});
}
} catch (_) { /* ignore */ }
// Flatpickr (multiple dates)
flatpickr("#datesInput", {
mode: "multiple",
@ -250,6 +427,40 @@ document.addEventListener('DOMContentLoaded', function () {
toggleUIOnCategorieChange();
});
// === Bridge: receive submissions from compact embed and perform calculation ===
window.addEventListener('message', (ev) => {
const msg = ev.data;
if (!msg || msg.type !== 'simulateur_compact_submit' || !msg.data) return;
try {
// Map payload to full form controls
const d = msg.data;
const setVal = (id, val) => { const el = document.getElementById(id); if (el) el.value = val ?? ''; };
setVal('conventionSelect', d.ccn);
setVal('categorieSelect', d.categorie);
setVal('statutSelect', d.statut);
const abYes = document.getElementById('abattementOui');
const abNo = document.getElementById('abattementNon');
if (d.abattement === 'oui' && abYes) abYes.checked = true; else if (abNo) abNo.checked = true;
const profSel = document.getElementById('professionSelect'); if (profSel) profSel.value = d.profession || '';
setVal('cachetsInput', d.cachets);
setVal('heuresInput', d.heures);
setVal('datesInput', d.dates);
setVal('montantInput', d.montant);
const type = d.type || 'brut';
const typeRadio = document.querySelector(`input[name="type"][value="${type}"]`); if (typeRadio) typeRadio.checked = true;
// Apply dependent UI toggles
toggleUIOnCategorieChange();
const abEvent = new Event('change');
document.querySelectorAll('input[name="abattement"]').forEach(r => r.dispatchEvent(abEvent));
// Trigger calculation
document.getElementById('calcBtn')?.click();
} catch (e) {
console.error('Compact bridge error', e);
}
});
/* ====== Constantes générales ====== */
const SMIC_HORAIRE = 11.88; // 2025
const PHSS = 29; // €