espace-paie-odentas/app/(app)/minima-ccn/ccnpa/categorie-b-hors-data-part3.tsx

334 lines
16 KiB
TypeScript

"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>
);
}