espace-paie-odentas/app/(app)/minima-ccn/ccnpa/page.tsx

351 lines
12 KiB
TypeScript

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