// ✅ Importations nécessaires import { useState, useEffect, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { Box, Typography, Button, Grid, Card, CardContent, CardMedia, TextField, Stack, Chip, CircularProgress, Tooltip, } from "@mui/material"; import { Add, ArrowBack, Edit, Delete } from "@mui/icons-material"; import api from "../../api"; import { getToken } from "../../auth"; import { deletePost } from "../../wordpress"; import "../../assets/styleCours.css"; const BUTTON_BASE_SX = Object.freeze({ borderRadius: 999, textTransform: "none", fontWeight: 600, letterSpacing: 0.3, display: "inline-flex", alignItems: "center", gap: 0.75, transition: "transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease", "&:hover": { transform: "translateY(-1px)", }, "&:active": { transform: "translateY(0)", }, }); const BUTTON_SIZE_SX = Object.freeze({ small: { px: 2.1, py: 0.55, fontSize: "0.78rem", minHeight: 30, }, medium: { px: 2.8, py: 0.8, fontSize: "0.86rem", minHeight: 36, }, large: { px: 3.4, py: 1, fontSize: "0.94rem", minHeight: 42, }, }); const BUTTON_VARIANT_SX = Object.freeze({ primary: { background: "linear-gradient(135deg, #315397, #7bc0ff)", color: "#ffffff", border: "1px solid rgba(255,255,255,0.35)", boxShadow: "0 18px 28px -18px rgba(11, 26, 61, 0.55)", "&:hover": { background: "linear-gradient(135deg, #26467d, #6fb6ff)", boxShadow: "0 22px 32px -18px rgba(11, 26, 61, 0.6)", }, }, outline: { background: "rgba(255,255,255,0.84)", color: "#315397", border: "1px solid rgba(49,83,151,0.26)", "&:hover": { background: "rgba(255,255,255,0.96)", borderColor: "rgba(49,83,151,0.46)", boxShadow: "0 14px 22px -18px rgba(11, 26, 61, 0.4)", }, }, ghost: { background: "rgba(255,255,255,0.16)", color: "rgba(255,255,255,0.92)", border: "1px solid rgba(255,255,255,0.28)", backdropFilter: "blur(8px)", "&:hover": { background: "rgba(255,255,255,0.24)", boxShadow: "0 18px 28px -18px rgba(6, 18, 38, 0.45)", }, }, danger: { background: "linear-gradient(135deg, #f05b6b, #ff8a80)", color: "#3a0a0f", border: "1px solid rgba(240,91,107,0.35)", boxShadow: "0 18px 26px -18px rgba(105, 21, 33, 0.45)", "&:hover": { background: "linear-gradient(135deg, #e34659, #ff7575)", boxShadow: "0 22px 30px -18px rgba(105, 21, 33, 0.52)", }, }, }); const composeButtonSx = (variant, size = "medium") => ({ ...BUTTON_BASE_SX, ...(BUTTON_SIZE_SX[size] || BUTTON_SIZE_SX.medium), ...(BUTTON_VARIANT_SX[variant] || BUTTON_VARIANT_SX.primary), }); const GestionArticles = () => { const [posts, setPosts] = useState([]); const [searchTerm, setSearchTerm] = useState(""); const [loading, setLoading] = useState(true); const navigate = useNavigate(); // ✅ Vérifier si l'utilisateur est connecté useEffect(() => { if (!getToken()) { navigate("/admin/login"); } }, [navigate]); // ✅ Récupérer les articles et leurs images associées useEffect(() => { const fetchPosts = async () => { try { setLoading(true); const response = await api.get( "wp/v2/posts?_fields=id,title,excerpt,featured_media,date,status" ); const postsData = response.data; const postsWithImages = await Promise.all( postsData.map(async (post) => { if (post.featured_media) { try { const mediaResponse = await api.get(`wp/v2/media/${post.featured_media}`); return { ...post, image: mediaResponse.data.source_url, imageId: post.featured_media }; } catch (error) { console.error(`❌ Erreur chargement image article ${post.id}:`, error); return { ...post, image: null, imageId: null }; } } return { ...post, image: null, imageId: null }; }) ); setPosts(postsWithImages); } catch (error) { console.error("❌ Erreur chargement des articles :", error); } finally { setLoading(false); } }; fetchPosts(); }, []); // ✅ Supprimer un article et son image associée const stripHtml = (value = "") => value.replace(/(<([^>]+)>)/gi, "").replace(/ /gi, " ").trim(); const previewExcerpt = (value = "", limit = 160) => { const clean = stripHtml(value); return clean.length > limit ? `${clean.slice(0, limit)}…` : clean; }; const formatDate = (value) => { if (!value) { return "Date inconnue"; } try { return new Intl.DateTimeFormat("fr-FR", { year: "numeric", month: "long", day: "numeric", }).format(new Date(value)); } catch { return "Date inconnue"; } }; const handleDeletePost = async (postId, imageId) => { const confirmed = window.confirm( "Supprimer cet article et son visuel associé ?" ); if (!confirmed) { return; } try { await deletePost(postId, imageId); setPosts((prev) => prev.filter((post) => post.id !== postId)); alert("✅ Article supprimé avec succès !"); } catch (error) { console.error("❌ Erreur suppression article :", error); alert("⚠ Erreur : impossible de supprimer l'article."); } }; const handleCreate = () => navigate("/admin/create-post"); const handleBack = () => navigate("/admin/dashboard"); const handleEdit = (postId) => navigate(`/admin/edit-post/${postId}`); const filteredPosts = useMemo(() => { const term = searchTerm.trim().toLowerCase(); if (!term) { return posts; } return posts.filter((post) => { const title = post.title?.rendered?.toLowerCase?.() || ""; const excerpt = stripHtml(post.excerpt?.rendered || "").toLowerCase(); return title.includes(term) || excerpt.includes(term); }); }, [posts, searchTerm]); const stats = useMemo(() => { const total = posts.length; const withImage = posts.reduce( (acc, post) => (post.image ? acc + 1 : acc), 0 ); const latest = posts.reduce((current, candidate) => { if (!candidate?.date) { return current; } if (!current) { return candidate; } return new Date(candidate.date) > new Date(current.date) ? candidate : current; }, null); return { total, withImage, withoutImage: total - withImage, latestLabel: latest?.title?.rendered || "Aucun article", latestDate: latest?.date ? formatDate(latest.date) : "—", }; }, [posts]); return ( Contenu éditorial Gestion des articles Consultez les publications WordPress, ajustez vos contenus et maintenez un flux éditorial cohérent pour Octopus. Total articles {stats.total} {filteredPosts.length} résultat(s) après filtre Articles illustrés {stats.withImage} {stats.withoutImage} sans visuel associé Dernière publication {stats.latestLabel} {stats.latestDate} Rechercher un article Filtrez par titre ou résumé pour retrouver un contenu en quelques secondes. setSearchTerm(event.target.value)} sx={{ maxWidth: 380, "& .MuiOutlinedInput-root": { borderRadius: 999, backgroundColor: "rgba(255,255,255,0.92)", }, }} /> {loading ? ( ) : filteredPosts.length === 0 ? ( Aucun article trouvé Ajustez votre recherche ou créez un nouvel article pour alimenter le blog. ) : ( {filteredPosts.map((post) => ( {post.title.rendered} {previewExcerpt(post.excerpt?.rendered)} ))} )} ); }; export default GestionArticles;