ajout du paiement par virement
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { getOrderDetails, getOrderUpgradables, createTicket, getHelpdesks } from '../../services/api';
|
||||
import { Server, ArrowUpRight, ArrowDownRight, Trash2, ShieldCheck, Loader, AlertCircle } from 'lucide-react';
|
||||
import NotificationModal from '../../components/ui/NotificationModal';
|
||||
|
||||
export default function OrderManagement() {
|
||||
const { orderId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [order, setOrder] = useState(null);
|
||||
const [upgradables, setUpgradables] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [customAlert, setCustomAlert] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrderData = async () => {
|
||||
try {
|
||||
const orderData = await getOrderDetails(orderId);
|
||||
setOrder(orderData);
|
||||
|
||||
// Si la commande est active, on cherche les upgrades possibles
|
||||
if (orderData.status === 'active') {
|
||||
const upgradeData = await getOrderUpgradables(orderId);
|
||||
if (upgradeData) setUpgradables(upgradeData);
|
||||
}
|
||||
} catch (err) {
|
||||
setCustomAlert({ type: 'error', title: 'Erreur', message: "Impossible de récupérer les données du contrat." });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchOrderData();
|
||||
}, [orderId]);
|
||||
|
||||
const handleUpgrade = async (targetProductId, targetProductName) => {
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
// 1. On récupère le département de support réseau (ou le premier disponible)
|
||||
const hdesks = await getHelpdesks();
|
||||
const targetHelpdeskId = Object.keys(hdesks)[1] || 1;
|
||||
|
||||
// 2. On formate une requête automatique claire pour l'administrateur
|
||||
const subject = `[AUTO] Migration d'instance - ${order.title}`;
|
||||
const message = `SYSTÈME NEXUS : Demande de migration automatisée.\n\nLe client demande le passage de l'instance #${order.id} (${order.title}) vers le forfait ID : ${targetProductId} (${targetProductName}).\n\nMerci de générer la facture de prorata depuis l'interface d'administration.`;
|
||||
|
||||
// 3. On génère le ticket silencieusement
|
||||
await createTicket(subject, message, targetHelpdeskId);
|
||||
|
||||
setCustomAlert({
|
||||
type: 'success',
|
||||
title: 'Requête Transmise',
|
||||
message: `La demande de migration vers ${targetProductName} a été envoyée aux ingénieurs GISE.\n\nUne facture de prorata sera générée dans votre espace d'ici quelques minutes. Vous serez notifié par e-mail.`
|
||||
});
|
||||
|
||||
setTimeout(() => navigate('/support'), 4500);
|
||||
} catch (err) {
|
||||
setCustomAlert({ type: 'error', title: 'Échec de la transmission', message: err.message });
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = async () => {
|
||||
if (!window.confirm("Alerte : Êtes-vous sûr de vouloir renoncer à cet abonnement ? Le service restera actif jusqu'à la fin de la période facturée.")) return;
|
||||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const hdesks = await getHelpdesks();
|
||||
const targetHelpdeskId = Object.keys(hdesks)[1] || 1;
|
||||
|
||||
const subject = `[AUTO] Demande de résiliation - ${order.title}`;
|
||||
const message = `SYSTÈME NEXUS : Demande de résiliation automatisée.\n\nLe client demande la non-reconduction du contrat #${order.id} (${order.title}).\n\nL'instance doit être détruite à la fin du cycle de facturation actuel.`;
|
||||
|
||||
await createTicket(subject, message, targetHelpdeskId);
|
||||
|
||||
setCustomAlert({
|
||||
type: 'success',
|
||||
title: 'Résiliation Programmée',
|
||||
message: "L'ordre d'arrêt a été transmis. L'abonnement ne sera pas renouvelé à la fin du cycle et l'instance sera détruite."
|
||||
});
|
||||
|
||||
setTimeout(() => navigate('/support'), 4500);
|
||||
} catch (err) {
|
||||
setCustomAlert({ type: 'error', title: 'Échec', message: err.message });
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="flex justify-center items-center mt-20 text-cyan-400 font-mono"><Loader className="w-8 h-8 animate-spin mr-3"/> ANALYSE DU CONTRAT...</div>;
|
||||
}
|
||||
|
||||
if (!order) return null;
|
||||
|
||||
// FOSSBilling renvoie souvent les upgradables sous forme de { "id_produit": "Nom du produit" }
|
||||
const upgradeOptions = Object.entries(upgradables);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto mt-12 p-8 bg-gray-900 border border-gray-800 rounded-2xl shadow-2xl">
|
||||
<div className="flex items-center gap-3 mb-8 border-b border-gray-800 pb-6">
|
||||
<ShieldCheck className="w-8 h-8 text-cyan-400" />
|
||||
<div>
|
||||
<h2 className="text-2xl font-black text-white tracking-wider uppercase">Gestion du contrat : {order.title}</h2>
|
||||
<p className="text-gray-400 font-mono text-sm">Contrat réseau #{order.id} — Renouvellement : {order.period}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTIONS DES UPGRADES / DOWNGRADES */}
|
||||
<h3 className="text-sm font-bold text-gray-400 tracking-widest uppercase mb-4 font-mono">Moduler l'infrastructure</h3>
|
||||
|
||||
{upgradeOptions.length === 0 ? (
|
||||
<div className="bg-black/50 p-6 rounded-xl border border-gray-800 mb-8 text-center font-mono text-sm text-gray-500">
|
||||
Aucune modification de forfait n'est disponible pour cette instance actuellement.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-10">
|
||||
{upgradeOptions.map(([targetId, targetName]) => (
|
||||
<div key={targetId} className="bg-black/40 border border-gray-800 p-5 rounded-xl flex justify-between items-center hover:border-cyan-500/50 transition-colors">
|
||||
<span className="font-bold text-white font-mono">{targetName}</span>
|
||||
<button
|
||||
onClick={() => handleUpgrade(targetId, targetName)}
|
||||
disabled={isProcessing}
|
||||
className="bg-cyan-500/10 text-cyan-400 hover:bg-cyan-500 hover:text-black border border-cyan-500/30 px-4 py-2 rounded text-xs font-bold transition-all disabled:opacity-50 uppercase tracking-widest flex items-center gap-1"
|
||||
>
|
||||
Migrer <ArrowUpRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="col-span-full mt-2 text-[10px] text-gray-500 font-mono text-center">
|
||||
* Le système calculera automatiquement le prorata. Une facture de différence ou une note de crédit sera générée selon votre choix.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ZONE DANGER : RÉSILIATION */}
|
||||
<div className="border-t border-red-900/30 pt-8 mt-4">
|
||||
<h3 className="text-sm font-bold text-red-500 tracking-widest uppercase mb-4 font-mono flex items-center gap-2">
|
||||
<AlertCircle className="w-4 h-4" /> Zone Critique
|
||||
</h3>
|
||||
<div className="bg-red-950/20 border border-red-900/30 p-6 rounded-xl flex flex-col md:flex-row justify-between items-center">
|
||||
<div>
|
||||
<h4 className="text-white font-bold mb-1">Renoncer à l'abonnement</h4>
|
||||
<p className="text-xs text-gray-400 font-mono max-w-md">
|
||||
L'instance restera opérationnelle jusqu'à la fin de la période facturée. Elle sera ensuite détruite et les données effacées.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
disabled={isProcessing}
|
||||
className="mt-4 md:mt-0 bg-red-500/10 hover:bg-red-500 text-red-400 hover:text-white border border-red-500/30 px-6 py-3 rounded text-xs font-bold tracking-widest uppercase transition flex items-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" /> Résilier l'instance
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NotificationModal notification={customAlert} onClose={() => setCustomAlert(null)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user