diff --git a/src/components/dashboard/SubscriptionManager.jsx b/src/components/dashboard/WebServiceSubscriptionManager.jsx similarity index 99% rename from src/components/dashboard/SubscriptionManager.jsx rename to src/components/dashboard/WebServiceSubscriptionManager.jsx index a7a9cd0..7707399 100644 --- a/src/components/dashboard/SubscriptionManager.jsx +++ b/src/components/dashboard/WebServiceSubscriptionManager.jsx @@ -1,9 +1,9 @@ import { useState, useEffect } from 'react'; import { CreditCard, Check, Globe, Loader, ToggleLeft, ShieldAlert, Calendar, TrendingUp, TrendingDown, RefreshCcw } from 'lucide-react'; -import { getProductList, getHostingServiceDetails } from '../../services/api'; +import { getProductList, getServiceDetails } from '../../services/api'; const CUSTOM_API_BASE_URL = import.meta.env.VITE_CUSTOM_API_BASE_URL || ''; -export default function SubscriptionManager({ order, onClose, onRefresh, onAlert }) { +export default function WebServiceSubscriptionManager({ order, onClose, onRefresh, onAlert }) { const [loading, setLoading] = useState(false); const [cancelLoading, setCancelLoading] = useState(false); const [catalog, setCatalog] = useState([]); @@ -20,7 +20,7 @@ export default function SubscriptionManager({ order, onClose, onRefresh, onAlert const fetchDetails = async () => { setIsDetailsLoading(true); try { - const data = await getHostingServiceDetails(order.id); + const data = await getServiceDetails(order.id); setServiceDetails(data); } catch (err) { console.error("Erreur détails service:", err); diff --git a/src/pages/app/Dashboard.jsx b/src/pages/app/Dashboard.jsx index b78f41b..58b2d24 100644 --- a/src/pages/app/Dashboard.jsx +++ b/src/pages/app/Dashboard.jsx @@ -1,11 +1,13 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { getClientOrders } from '../../services/api'; -import { AlertCircle, Loader } from 'lucide-react'; +import { AlertCircle, Loader, FileText, X } from 'lucide-react'; -// Importation des composants isolés +// Importation des composants import DashboardServiceCard from '../../components/dashboard/DashboardServiceCard'; import NewServiceCard from '../../components/dashboard/NewServiceCard'; +import NotificationModal from '../../components/ui/NotificationModal'; +import WebServiceSubscriptionManager from '../../components/dashboard/WebServiceSubscriptionManager'; export default function Dashboard() { const navigate = useNavigate(); @@ -13,54 +15,82 @@ export default function Dashboard() { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - // Chargement des données à l'ouverture du Sas - useEffect(() => { - const fetchInventory = async () => { - try { - const data = await getClientOrders(); - - if (data.list) { - // LE FILTRE CHIRURGICAL PAR PREFIXE - const filteredOrders = data.list.filter(order => { - const title = (order.title || '').toLowerCase(); - const type = (order.type || '').toLowerCase(); + const [activeSubscriptionModal, setActiveSubscriptionModal] = useState(null); + const [customAlert, setCustomAlert] = useState(null); - const isGhostProduct = - type === 'domain' || - title.startsWith('domain ') || - title.startsWith('domaine ') || - title.startsWith('enregistrement '); - - return !isGhostProduct; - }); + const triggerAlert = (title, message, type = "info") => setCustomAlert({ title, message, type }); - setOrders(filteredOrders); - } else { - setOrders([]); - } - } catch (err) { - setError(err.message || "Impossible de récupérer la télémétrie des services."); - } finally { - setIsLoading(false); + const fetchInventory = async () => { + setIsLoading(true); + try { + const data = await getClientOrders(); + + if (data.list) { + // LE FILTRE CHIRURGICAL PAR PREFIXE + const filteredOrders = data.list.filter(order => { + const title = (order.title || '').toLowerCase(); + const type = (order.type || '').toLowerCase(); + + const isGhostProduct = + type === 'domain' || + title.startsWith('domain ') || + title.startsWith('domaine ') || + title.startsWith('enregistrement '); + + return !isGhostProduct; + }); + + setOrders(filteredOrders); + } else { + setOrders([]); } - }; + } catch (err) { + setError(err.message || "Impossible de récupérer la télémétrie des services."); + } finally { + setIsLoading(false); + } + }; + useEffect(() => { fetchInventory(); }, []); + // 🌟 Plus besoin de charger les IPs, on ouvre juste la modale comptable ! + const handleManageSubscription = (order) => { + // 1. Détection du type de service + const titleLower = (order.title || '').toLowerCase(); + const isVPS = titleLower.includes('vps') || titleLower.includes('compute'); + const isCloud = titleLower.includes('cloud') || titleLower.includes('nextcloud'); + const isDB = titleLower.includes('db') || titleLower.includes('base') || titleLower.includes('sql'); + + // C'est un hébergement web s'il ne correspond à aucun des cas ci-dessus + const isWeb = !isVPS && !isCloud && !isDB; + + // 2. Logique de blocage + if (isWeb) { + // On ouvre la modale comptable uniquement pour le WEB + setActiveSubscriptionModal(order); + } else { + // On affiche une notification pour les autres types d'instances + triggerAlert( + "Action non disponible", + "La modification autonome d'abonnement est actuellement exclusive aux Hébergements Web. Pour restructurer cette instance, veuillez contacter les ingénieurs via le Centre de Support.", + "info" + ); + } + }; + return ( -
- +

TABLEAU DE BORD

-

Aperçu de vos accréditations réseau et infrastructures.

+

Gestion financière et accréditations de vos infrastructures.

- {/* GESTION DES ERREURS & CHARGEMENT */} {isLoading && (
- Synchronisation avec l'orchestrateur en cours... + Synchronisation avec le registre comptable...
)} @@ -71,24 +101,44 @@ export default function Dashboard() {
)} - {/* GRILLE DES SERVICES */} {!isLoading && !error && (
- - {/* Boucle sur les composants isolés */} {orders.map((order) => ( - navigate(`/services/${order.id}`)} + handleManageSubscription(order)} /> ))} - - {/* Le composant carte d'ajout */} navigate('/store')} /> -
)} + + {/* 🌟 LA MODALE DE GESTION D'ABONNEMENT */} + {activeSubscriptionModal && ( +
+
+
+

+ + GESTION DE L'ABONNEMENT +

+ +
+ + setActiveSubscriptionModal(null)} + onRefresh={fetchInventory} + onAlert={triggerAlert} + /> +
+
+ )} + + setCustomAlert(null)} />
); } \ No newline at end of file diff --git a/src/pages/app/Services.jsx b/src/pages/app/Services.jsx index 75ab49b..e11a169 100644 --- a/src/pages/app/Services.jsx +++ b/src/pages/app/Services.jsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; -import { getMyServices, getHostingServiceDetails } from '../../services/api'; +import { getClientOrders, getOrderDetails } from '../../services/api'; import { useVPC } from '../../services/useVPC'; import { Server, Globe, Folder, Trash2, Loader, Plus, AlertCircle } from 'lucide-react'; @@ -25,12 +25,12 @@ export default function Services() { const fetchServices = useCallback(async () => { try { - const data = await getMyServices(); + const data = await getClientOrders(); if (data.list && data.list.length > 0) { const detailedServices = await Promise.all(data.list.map(async (order) => { let hDetails = null; if (order.status === 'active' && (order.plugin === 'hosting' || (order.title || '').toLowerCase().includes('vps'))) { - try { hDetails = await getHostingServiceDetails(order.id); } catch (e) {} + try { hDetails = await getOrderDetails(order.id); } catch (e) {} } return { ...order, hostingDetails: hDetails }; })); @@ -65,7 +65,7 @@ export default function Services() { } else if (isDB) { window.open('https://pma.gise.be/', '_blank'); } else if (isVPS || isWeb) { - const freshDetails = await getHostingServiceDetails(service.id); + const freshDetails = await getOrderDetails(service.id); setActiveServiceModal({ type: isVPS ? 'vps' : 'web', data: service, details: freshDetails }); } } catch (err) { triggerAlert("Erreur", err.message, "error"); } diff --git a/src/services/api.js b/src/services/api.js index d546f75..07554f9 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,7 +1,7 @@ const BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; const CUSTOM_API_BASE_URL = import.meta.env.VITE_CUSTOM_API_BASE_URL || ''; -// Le moteur de requête unifié et intelligent +// Le moteur de requête const apiCall = async (endpoint, param2 = 'GET', param3 = null) => { let method = 'GET'; let body = null; @@ -64,24 +64,6 @@ const apiCall = async (endpoint, param2 = 'GET', param3 = null) => { } }; -// ========================================== -// ROUTES FOSSBILLING NATIVES (BACKTICKS INTÉGRÉS) -// ========================================== - -export const loginClient = (email, password) => - apiCall(`${BASE_URL}/api/guest/client/login`, { email, password }); - -// Récupère la liste des services/commandes du client -export const getClientOrders = () => - apiCall(`${BASE_URL}/api/client/order/get_list`); - -// Récupère les détails techniques du service rattaché à une commande -export const getOrderService = (order_id) => - apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id }); - -// Récupère le catalogue public des produits FOSSBilling -export const getProductList = () => - apiCall(`${BASE_URL}/api/guest/product/get_list`); // Vide le panier (Action PUBLIQUE : on passe par l'API Guest) export const resetCart = async () => { @@ -100,22 +82,6 @@ export const resetCart = async () => { } }; -// Ajoute un produit au panier avec ses options étalées à la racine -export const addToCart = (productId, period, additionalData = {}) => - apiCall(`${BASE_URL}/api/guest/cart/add_item`, 'POST', { - id: productId, - period: period, - ...additionalData // Les 3 petits points "étalent" le contenu de l'objet - }); - -// Valide le panier (Action PRIVÉE : on reste sur l'API Client pour générer la facture) -export const checkoutCart = () => - apiCall(`${BASE_URL}/api/client/cart/checkout`, 'POST'); - -// Récupère les informations du client connecté -export const getClientProfile = () => - apiCall(`${BASE_URL}/api/client/profile/get`, 'GET'); - // ========================================== // ROUTES PERSONNALISÉES (CUSTOM API) // ========================================== @@ -144,18 +110,6 @@ export const registerUnifiedClient = async (email, username, password, firstName } } -// Récupère la liste de toutes les commandes actives du client -export const getMyServices = () => - apiCall(`${BASE_URL}/api/client/order/get_list`, 'GET'); - -// Récupère les détails secrets d'un service (dont le mot de passe HestiaCP/VPS) -export const getServiceDetails = (orderId) => - apiCall(`${BASE_URL}/api/client/order/get`, 'POST', { id: orderId }); - -// Récupère les secrets spécifiques du service physique attaché à une commande -export const getHostingServiceDetails = (orderId) => - apiCall(`${BASE_URL}/api/client/order/service`, 'POST', { id: orderId }); - // Force la réinitialisation du mot de passe sur le serveur distant (HestiaCP) export const resetHostingPassword = (orderId, newPassword) => apiCall(`${BASE_URL}/api/client/servicehosting/change_password`, 'POST', { @@ -191,33 +145,169 @@ export const launchSSOGateway = (username, password) => { document.body.removeChild(form); }; +// ####################################################################################### + // ========================================== -// ROUTES SUPPORT FOSSBILLING (NATIVES) +// GESTION DES ACCES // ========================================== -// Récupère la liste de tous les tickets du client -export const getClientTickets = () => - apiCall(`${BASE_URL}/api/client/support/ticket_get_list`, 'GET'); +// Connexion +export const loginClient = (email, password) => + apiCall(`${BASE_URL}/api/guest/client/login`, { email, password }); -// Récupère le contenu et les messages d'un ticket spécifique -export const getTicketDetails = (ticketId) => - apiCall(`${BASE_URL}/api/client/support/ticket_get`, 'POST', { id: ticketId }); +// Deconnexion +export const logoutClient = () => + apiCall(`${BASE_URL}/api/client/profile/logout`); -// Crée un nouveau ticket de support + +// ========================================== +// GESTION DU CLIENT +// ========================================== + +// Recuperer les informations du client +export const getClientProfile = () => + apiCall(`${BASE_URL}/api/client/profile/get`, 'GET'); + +// Update les informations du client +export const updateClientProfile = (email, last_name, aid, gender, country, city, birthday, company, company_vat, company_number, type, address_1, address_2, postcode, state, phone, phone_cc, document_type, document_nr, notes, lang, custom_1, custom_2, custom_3, custom_4, custom_5, custom_6, custom_7, custom_8, custom_9, custom_10) => + apiCall(`${BASE_URL}/api/client/profile/update`, { + email: email, + last_name: last_name, + aid: aid, + gender: gender, + country: country, + city: city, + birthday: birthday, + company: company, + company_vat: company_vat, + company_number: company_number, + type: type, + address_1: address_1, + address_2: address_2, + postcode: postcode, + state: state, + phone: phone, + phone_cc: phone_cc, + document_type: document_type, + document_nr: document_nr, + notes: notes, + lang: lang, + custom_1: custom_1, + custom_2: custom_2, + custom_3: custom_3, + custom_4: custom_4, + custom_5: custom_5, + custom_6: custom_6, + custom_7: custom_7, + custom_8: custom_8, + custom_9: custom_9, + custom_10: custom_10 + + }); + +// Changer le mot de passe +export const changeClientPassword = (current_password, new_password, confirm_password) => + apiCall(`${BASE_URL}/api/client/profile/change_password`, {current_password:current_password, new_password:new_password, confirm_password:confirm_password}); + + + +// ========================================== +// GESTION DES ABONNEMENTS +// ========================================== + +// Souscription + +// Resiliation + +// Suppression +export const deleteWrongOrder = (order_id) => + apiCall(`${BASE_URL}/api/client/order/delete`, { id: order_id }); + +// Promotion (Surclassement) +// Lister +export const getUpgrades = (order_id) => + apiCall(`${BASE_URL}/api/client/order/upgradables`, { id: order_id }); + +// Demotion (Declassement) + +// Lister les abonnements du client +export const getClientOrders = () => + apiCall(`${BASE_URL}/api/client/order/get_list`); + +// Lister les produits disponibles +export const getProductList = () => + apiCall(`${BASE_URL}/api/guest/product/get_list`); + +// Recuperer les details d'un abonnement +export const getOrderDetails = (order_id) => + apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id }); + +// Recuperer les details secrets d'un abonnement +export const getServiceDetails = (orderId) => + apiCall(`${BASE_URL}/api/client/order/get`, 'POST', { id: orderId }); + + +// ========================================== +// GESTION DU PANIER +// ========================================== + +// Ajouter au panier +export const addToCart = (productId, period, additionalData = {}) => + apiCall(`${BASE_URL}/api/guest/cart/add_item`, 'POST', { + id: productId, + period: period, + ...additionalData // Les 3 petits points "étalent" le contenu de l'objet + }); + +// Checkout +export const checkoutCart = () => + apiCall(`${BASE_URL}/api/client/cart/checkout`, 'POST'); + + +// ========================================== +// GESTION DES FACTURES +// ========================================== + +// Creation +// Suppression +// Modification +// Lister +export const getInvoiceList = () => + apiCall(`${BASE_URL}/api/client/invoice/get_list`, 'GET'); + +// Lire +export const getInvoiceDetails = (InvoiceHash) => + apiCall(`${BASE_URL}/api/client/invoice/get`, 'GET'); + + +// ========================================== +// GESTION DES TICKETS +// ========================================== + +// Creation export const createTicket = (subject, message, helpdesk_id) => apiCall(`${BASE_URL}/api/client/support/ticket_create`, 'POST', { support_helpdesk_id: helpdesk_id, subject: subject, content: message }); +// Suppression -// Répond à un ticket existant +// Lister les tickets +export const getClientTickets = () => + apiCall(`${BASE_URL}/api/client/support/ticket_get_list`, 'GET'); + +// Lister les helpdesks +export const getHelpdesks = () => + apiCall(`${BASE_URL}/api/client/support/helpdesk_get_pairs`, 'GET'); + +// Lire +export const getTicketDetails = (ticketId) => + apiCall(`${BASE_URL}/api/client/support/ticket_get`, 'POST', { id: ticketId }); + +// Repondre export const replyTicket = (ticketId, message) => apiCall(`${BASE_URL}/api/client/support/ticket_reply`, 'POST', { id: ticketId, content: message - }); - -// Récupère la liste dynamique des départements (Helpdesks) configurés sur FOSSBilling -export const getHelpdesks = () => - apiCall(`${BASE_URL}/api/client/support/helpdesk_get_pairs`, 'GET'); \ No newline at end of file + }); \ No newline at end of file