✨ Nouvelles fonctionnalités - Page de gestion des avenants (/staff/avenants) - Page de détail d'un avenant (/staff/avenants/[id]) - Création d'avenants (objet, durée, rémunération) - Génération automatique de PDF d'avenant - Signature électronique via DocuSeal (employeur puis salarié) - Changement manuel du statut d'un avenant - Suppression d'avenants 🔧 Routes API - POST /api/staff/amendments/create - Créer un avenant - POST /api/staff/amendments/generate-pdf - Générer le PDF - POST /api/staff/amendments/[id]/send-signature - Envoyer en signature - POST /api/staff/amendments/[id]/change-status - Changer le statut - POST /api/webhooks/docuseal-amendment - Webhook après signature employeur - GET /api/signatures-electroniques/avenants - Liste des avenants en signature 📧 Système email universel v2 - Migration vers le système universel v2 pour les emails d'avenants - Template 'signature-request-employee-amendment' pour salariés - Insertion automatique dans DynamoDB pour la Lambda - Mise à jour automatique du statut dans Supabase 🗄️ Base de données - Table 'avenants' avec tous les champs (objet, durée, rémunération) - Colonnes de notification (last_employer_notification_at, last_employee_notification_at) - Liaison avec cddu_contracts 🎨 Composants - AvenantDetailPageClient - Détail complet d'un avenant - ChangeStatusModal - Changement de statut manuel - SendSignatureModal - Envoi en signature - DeleteAvenantModal - Suppression avec confirmation - AvenantSuccessModal - Confirmation de création 📚 Documentation - AVENANT_EMAIL_SYSTEM_MIGRATION.md - Guide complet de migration 🐛 Corrections - Fix parsing défensif dans Lambda AWS - Fix récupération des données depuis DynamoDB - Fix statut MFA !== 'verified' au lieu de === 'unverified'
100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { X, AlertCircle } from "lucide-react";
|
|
|
|
interface ChangeStatusModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onConfirm: (newStatus: string) => void;
|
|
currentStatus: string;
|
|
isChanging: boolean;
|
|
numeroAvenant: string;
|
|
}
|
|
|
|
const STATUS_OPTIONS = [
|
|
{ value: "draft", label: "Brouillon", color: "bg-slate-100 text-slate-700 border-slate-300" },
|
|
{ value: "pending", label: "En attente", color: "bg-orange-100 text-orange-700 border-orange-300" },
|
|
{ value: "signed", label: "Signé", color: "bg-green-100 text-green-700 border-green-300" },
|
|
{ value: "cancelled", label: "Annulé", color: "bg-red-100 text-red-700 border-red-300" },
|
|
];
|
|
|
|
export default function ChangeStatusModal({
|
|
isOpen,
|
|
onClose,
|
|
onConfirm,
|
|
currentStatus,
|
|
isChanging,
|
|
numeroAvenant,
|
|
}: ChangeStatusModalProps) {
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
<div className="fixed inset-0 bg-black/30" onClick={onClose}></div>
|
|
<div className="relative bg-white rounded-xl shadow-xl max-w-md w-full p-6">
|
|
<button
|
|
onClick={onClose}
|
|
className="absolute top-4 right-4 p-2 hover:bg-slate-100 rounded-lg transition-colors"
|
|
>
|
|
<X className="h-5 w-5 text-slate-600" />
|
|
</button>
|
|
|
|
<div className="flex items-start gap-3 mb-6">
|
|
<div className="flex-shrink-0 w-10 h-10 bg-orange-100 rounded-lg flex items-center justify-center">
|
|
<AlertCircle className="h-6 w-6 text-orange-600" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-slate-900">
|
|
Changer le statut de l'avenant
|
|
</h3>
|
|
<p className="text-sm text-slate-600 mt-1">
|
|
Avenant {numeroAvenant}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-6">
|
|
<label className="text-sm font-medium text-slate-700 mb-3 block">
|
|
Sélectionnez le nouveau statut :
|
|
</label>
|
|
<div className="space-y-2">
|
|
{STATUS_OPTIONS.map((status) => (
|
|
<button
|
|
key={status.value}
|
|
onClick={() => onConfirm(status.value)}
|
|
disabled={isChanging || status.value === currentStatus}
|
|
className={`w-full px-4 py-3 rounded-lg border-2 text-left font-medium transition-all
|
|
${status.color}
|
|
${status.value === currentStatus
|
|
? "opacity-50 cursor-not-allowed"
|
|
: "hover:scale-[1.02] hover:shadow-md cursor-pointer"
|
|
}
|
|
${isChanging ? "opacity-50 cursor-not-allowed" : ""}
|
|
`}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<span>{status.label}</span>
|
|
{status.value === currentStatus && (
|
|
<span className="text-xs px-2 py-1 bg-white/50 rounded">
|
|
Actuel
|
|
</span>
|
|
)}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={onClose}
|
|
disabled={isChanging}
|
|
className="flex-1 px-4 py-2.5 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
Annuler
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|